Embedding the widget

Every way of putting account42 on a website comes down to the same two tags. The platform apps exist so a studio owner never has to see them.

Plain HTML: the two tags

<script src="https://app.account42.com/widget/v1/booking.js" defer></script>
<a42-booking key="a42pk_…"></a42-booking>

The widget renders the studio's live classes, takes a hold, collects the attendee's name and contact, confirms a free seat or sends a paid one to Stripe or PayPal Checkout, offers the waitlist when a class is full, and sells credit packs, memberships and class bundles where the studio has them. Everything it does goes through the v1 API with the same key.

Getting a key

A studio owner mints an embedded key through the connect handshake below (platform html), or copies the hosted page's snippet from their account42 settings. For the sandbox studio, see The sandbox.

WordPress

The plugin account42 Booking (WordPress 6.4+, PHP 8.0+, WooCommerce optional):

  1. Install and activate. Go to Settings → account42 and press Connect to account42.
  2. Sign in to account42, choose the studio, press Allow. You land back on the settings page, connected.
  3. Add the account42 Booking block to any page, or the shortcode [account42_booking] ([account42_booking lang="fr"] to fix the language).
  4. WooCommerce: tick Show the booking widget on WooCommerce product pages tagged account42 and tag the products. Bookings and payments stay with account42; nothing is added to the cart.

The plugin stores one option: the key, the studio's name, slug and hosted-page address, and the WooCommerce switch. Disconnect on the settings page revokes the key at account42 and clears the option; deleting the plugin does the same.

Wix

The app account42 Booking installs a dashboard page and a site widget:

  1. Install the app. In the site's dashboard open Apps → account42 Booking and press Connect to account42; sign in, choose the studio, press Allow. The page reads Connected to <studio>.
  2. In the editor, open + Add → Elements → Installed Apps (not Apps, which lists Wix's own solutions), find account42 Booking and drop the Booking widget on a page. After dropping the widget, select it and set its width to 100% in the Layout panel: one setting, once, and the schedule fills the section. Then Publish.
  3. The widget shows a labeled placeholder in the editor and in preview; on the published site it renders the real booking widget.

Wix initializes the widget's custom element only once it is near the viewport. A customer who comes back from checkout to a page where the widget sits below the fold sees the You're booked! confirmation once the widget scrolls into view; the booking itself is confirmed by the provider's webhook regardless. This is Wix's behavior, not ours.

The site must be published first: the key is tied to the published address. The dashboard page has Check connection, Reconnect (shown when the published address no longer matches the key's origin) and Disconnect.

Shopify

The app account42 Booking is an embedded admin page plus a theme app extension:

  1. Install the app. The admin page opens inside Shopify admin; press Connect to account42, sign in, choose the studio, press Allow. The page reads Connected to <studio>.
  2. Once connected, the page offers Add the booking block to your theme: one press opens the theme editor on the home page with the block already in a new Apps section (Shopify's theme-app-extension deep link, …/themes/current/editor?addAppBlockId=<app client id>/booking&target=newAppsSection). Press Save.
  3. For any other page, by hand: open Online Store → Themes → Customize; on the page you want, Add section → Apps → account42 Booking; press Save.
  4. Preview your storefront on the same page opens the store's primary domain. In the theme editor the block shows a labeled placeholder; on the storefront it renders the widget.

The key is kept on the shop's app installation (an app-data metafield the block reads). The storefront's primary domain is the key's origin; if the primary domain changes, the admin page offers Reconnect.

The connect handshake

Every connector works the same way. The platform never holds a secret of ours, never mints anything and never decides anything: it starts the handshake, keeps the one key it is handed, and renders one tag.

  1. Start. The platform sends the site owner to the consent page:
https://app.account42.com/connect
    ?platform=wordpress|wix|shopify|html
    &origin=<scheme://host[:port] of the PUBLISHED site, no path>
    &return=<a URL on the origin's host, or on app.account42.com>
    &state=<opaque, up to 256 characters>

All four are validated before anything renders: an unknown platform, an origin with a path, http anywhere but localhost, a return on another host or a long state gets a plain error page, never a redirect. The owner signs in, picks the studio if they own several, and presses Allow. A studio that is not live yet (no payment provider connected, or not switched on) is refused with This studio isn't live yet.

  1. Allow → the code. account42 revokes any earlier embedded key for the same studio, platform and origin, mints one new key for exactly that origin, and issues a connect code: 32 random bytes as hex, ten minutes, single use; only its SHA-256 is stored. The browser lands on <return>?a42_code=<code>&state=<state> (or ?a42_error=cancelled&state=<state>).
  1. Exchange. The platform's server posts the code, with no credential, to POST /api/v1/connect/exchange with body {"code":"…"} and receives { "data": { "key", "merchant": { "id", "slug", "name", "hosted_url" } } }. Anything wrong with the code is one opaque 404.
  1. Use, check, revoke. The key is used from its origin only. GET /api/v1/merchant with the key and the site's Origin is the "is my key valid" check. POST /api/v1/connect/revoke with Authorization: Bearer <key> revokes the key itself.

Origins and the Reconnect rule

An embedded key works only from the origin it was minted for: the browser's Origin header (or the Referer's origin on a plain navigation) must equal it, compared on scheme, host and port with default ports folded. A request from anywhere else is 403 origin_not_allowed; a request that states no origin is refused too.

If the site's published address changes, the key stops working there. The connector notices that the live origin no longer equals the key's origin and shows Reconnect, which is step 1 again with the new origin. Nothing is patched in place.

A secret key skips the origin check entirely: it is server-to-server.

A studio on Shopify or Wix usually has no Stripe account: Shopify Payments or Wix Payments is its gateway. So a store or site that installs the app sells paid classes through its own checkout by default, and the booking appears as an order in the platform's admin with refunds as platform refunds. The seat hold is still account42's; the platform only takes the money.

The setting is Payments for paid classes on the app's admin page (the Shopify App Home, the Wix dashboard page): the platform's checkout (the default) or the studio's own Stripe or PayPal connected on account42. It is per store or site; a studio that also books on its hosted page or on WordPress keeps using its own gateway there.

Shopify. The app keeps one product in the store, Class bookings (handle account42-class-bookings, tag account42), with one variant per class date at the class's price, synced before each checkout. POST /api/v1/sessions/:id/checkout from the store's embedded key answers 201 with a handoff instead of a checkout_url:

{ "handoff": { "platform": "shopify", "kind": "cart", "label": "Shopify",
  "variant_id": 4512…, "quantity": 1,
  "properties": { "_booking_id": "…", "_booking_token": "…" },
  "checkout_path": "/checkout" } }

The widget adds that variant to the store's cart (POST /cart/add.js, the two properties are private line item properties) and sends the customer to /checkout. Shopify's orders/paid webhook confirms the booking when the order's line carries the booking id and the paid amount equals the booking's; refunds/create records a refund made in the Shopify admin; a refund the studio issues from account42 is created on the order with refundCreate. The product must be published to the Online Store channel (the cart cannot take an unpublished variant); it is hidden from search engines, the sitemap and the store's search (seo.hidden), belongs to no collection, and stays reachable only by direct URL and in /collections/all. An order that buys a class-date variant that way, without the booking form, is still honored: the booking is made for the buyer if the class has a seat, otherwise the order is refunded and the buyer told the seat was no longer available. The same rule applies to a Wix order that carries the app's line item without a booking.

Wix. The checkout route creates a Cart V2 cart with one custom line item (the class, the seats, the price) and answers the site's checkout page for it:

{ "checkout_url": "https://<site>/checkout?…",
  "handoff": { "platform": "wix", "kind": "url", "label": "Wix", "url": "https://<site>/checkout?…" } }

Wix's Payment Status Updated event confirms the booking when the order's checkoutId is that cart and the line's price times quantity equals the booking's; Order Transactions Refund Completed records a refund made in the Wix dashboard; a refund from account42 goes through Refund Payments on the order.

On both platforms the customer lands on the platform's thank-you page; the confirmation email arrives from account42 as usual, and the booking page shows You're booked! when the customer returns to it (the widget remembers the checkout it started for two hours). A secret-key integration and the hosted page never take this path.

Hosted page versus embedded return

Every live studio has a hosted booking page at https://app.account42.com/b/<slug>, on our origin, rendering the same widget with the studio's own hosted key. The confirmation email's manage link and the waitlist claim link always point there.

A paid booking goes to Stripe or PayPal Checkout (or, on a Shopify store or Wix site set to the platform's checkout, to that checkout) and comes back:

The provider's return is UX only. A booking is confirmed by the provider's webhook, never by the return page, so a closed tab or a lost return cannot lose a payment.