Skip to main content

Overview

A customer account extension is one block your app adds to the account area of a signed in shopper. The storefront theme asks the Store API for the blocks on the current store, shows a card for each one, and opens your page in an iframe when the shopper chooses it. Your page is not given the shopper’s session cookie. Instead, the theme opens it with a short lived signed URL that names the store and the shopper. The signature is made with your app’s webhook signing secret, which never leaves the platform, and it tells you which shopper the block is acting for. Verify it before you read or change anything. The registration shape is the same as the checkout extensions: one row per installation, registered with your app token, with a merchant switch.

Register a block

An installed app registers one block per installation with its own app_ token. The registration is the customer-account kind of the checkout extension registry (AppCheckoutExtension), so it shares the row shape and the merchant view with the checkout kinds:
  • The row is bound to the installation behind the token, never to anything in the request. A second PUT replaces the registration.
  • GET returns 404 when nothing is registered. DELETE removes the row.
  • Uninstalling the app switches every extension kind off, this one included.
  • The URL is checked with the webhook URL guard: https only, no credentials, and no loopback or private address. The platform never calls the URL; the shopper’s browser loads it in an iframe. For local development, use a public https tunnel.
  • The release snapshot records the block’s title and icon. Publishing does not rewrite the live row; the Store API reads the AppCheckoutExtension row.

The merchant switch

A merchant can turn the block off without uninstalling the app:
Only the active flag can change from the merchant side. The URL stays the one your app registered. GET /api/v1/apps/installed/:id/checkout-extensions lists every extension the installation registered on that store.

How the theme gets your block

The Store API serves the blocks to the theme:
The shopper’s normal session is required. An anonymous request is 401, and a guest (ghost) account is refused with login_required. The store comes from the storefront request, as with the other storefront routes.
extensions is [] when the store has no active block, when every installation is uninstalled or has lost the scope, or when the app is switched off or has no signing secret. The endpoint returns no app secret and no shopper name, email, phone or address. The response is Cache-Control: private, no-store because every URL is minted for one shopper. The block metadata is cached for 60 seconds per store. A new or changed registration can take up to 60 seconds to appear, and a block that was switched off can linger for the same window. The signed URL is not cached: it is minted on every request, because it names one shopper. A store shows at most 10 blocks, oldest registration first.

The signed URL

The URL is your registered page with four query parameters added. Existing query parameters on your URL are preserved. The key is your app’s webhook signing secret for the installation’s environment: the development secret for a development install when your app has one, otherwise the production secret. The signature covers the expiry, the store and the shopper, so changing any of them invalidates it.
salesive_signature is base64url (URL safe, no padding), not the standard base64 used in the X-Salesive-Signature header. Use your language’s URL safe encoder, or convert + to - and / to _ and drop the padding.

Verify before you act

The URL is a bearer credential for its lifetime. Anyone who has it can use it until it expires, so do not log it, send it to another service, or put it in a page your shopper can copy. Treat the query parameters as untrusted until the signature checks out. A page that honours the contract does this on every request, including a reload or a second visit within the window:
  1. Read salesive_shop, salesive_customer, salesive_exp and salesive_signature from the URL. Reject the request when any is missing.
  2. Reject the request when salesive_exp is in the past, or more than 300 seconds ahead of your clock. The URL is minted with a 300 second lifetime, so a fresh one is always inside that window.
  3. Recompute the HMAC over the three values after URL decoding, using your webhook signing secret. Compare it with salesive_signature in constant time.
  4. Only after the signature matches, treat salesive_customer as the shopper and salesive_shop as the store.
  5. Never take a customer id from another query parameter, a cookie, a header or local storage. An unsigned value is not proof of anything, and a valid signature for one shopper must not be reused for another.
If verification fails, show a plain error and do nothing else. Do not fall back to an unsigned mode.
Node.js
The customerId is the platform user id of the signed in shopper. It is stable for that shopper on that store. It is not a Customer record id and carries no name, email, phone or address. If your page needs more, ask the shopper in your own page or use what your app already stored for that id.

What your page must do

  • Serve over https. The URL is embedded in an iframe on the storefront.
  • Verify the signature on every request, as above.
  • Work in an iframe, including a narrow mobile width. The theme controls the frame; your page owns its contents.
  • Not depend on document.referrer or on the shopper’s storefront cookies. Third party cookies may be blocked in an iframe, and the theme opens your page with no referrer.
  • Not assume the URL is single use. The same URL can be opened more than once until it expires.
  • Keep the block to one purpose. One installation has one customer account block, and a second registration replaces it.
  • Return an error page, not a redirect to the storefront login, when the signature is missing or invalid.

What other themes must do

The default theme is the first implementation. Any other theme opts in the same way:
  1. Call GET /api/v1/account/extensions on the account page with the shopper’s normal session, using the same client the theme already uses for /user.
  2. Read data.extensions.
  3. Render nothing when the list is empty. No heading, no empty state.
  4. Render each item as a card with title, appName and icon, and open url in an iframe when the shopper chooses it.
The iframe must send no referrer:
The response is private to one shopper. Do not cache it in a shared cache, and do not render it on a page that is not the signed in shopper’s own account area. The default theme’s implementation is in src/pages/account/account.jsx (the account-extensions query, the “From your apps” card and the extension dialog). It also sandboxes the iframe with sandbox="allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox". Start from a sandbox that is no wider than your block needs.

Not in this surface

Only the account page is covered. Order status page blocks, more than one block per app per store, and theme editor placement are not part of this surface yet. The block always renders below the account summary in the default theme.

Next steps

Checkout extensions

The registration shape this surface shares, with carrier rates and cart validation.

Storefront extensions

Scripts, embeds, blocks and the app proxy, the other ways an app reaches a store.

Webhooks

Where the signing secret comes from and how signature v2 works.

Scopes & permissions

What WRITE_CUSTOMER_ACCOUNT_EXTENSIONS grants and how the merchant sees it.