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 ownapp_ 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
PUTreplaces the registration. GETreturns404when nothing is registered.DELETEremoves the row.- Uninstalling the app switches every extension kind off, this one included.
- The URL is checked with the webhook URL guard:
httpsonly, 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
titleandicon. Publishing does not rewrite the live row; the Store API reads theAppCheckoutExtensionrow.
The merchant switch
A merchant can turn the block off without uninstalling the app: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: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:- Read
salesive_shop,salesive_customer,salesive_expandsalesive_signaturefrom the URL. Reject the request when any is missing. - Reject the request when
salesive_expis 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. - Recompute the HMAC over the three values after URL decoding, using your
webhook signing secret. Compare it with
salesive_signaturein constant time. - Only after the signature matches, treat
salesive_customeras the shopper andsalesive_shopas the store. - 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.
Node.js
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.referreror 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:- Call
GET /api/v1/account/extensionson the account page with the shopper’s normal session, using the same client the theme already uses for/user. - Read
data.extensions. - Render nothing when the list is empty. No heading, no empty state.
- Render each item as a card with
title,appNameandicon, and openurlin an iframe when the shopper chooses it.
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.
