Skip to main content
When a merchant installs your app, the platform provisions it for their workspace — it mints a per-workspace API key and hands it to you, so the merchant never pastes a key into a form. When they uninstall, the platform tells you to tear that workspace down. Those are the two moments this page is about, and getting them right is what makes your app safe to trust. The good news: you don’t wire the endpoints or verify the tokens yourself. The SDK’s mountInstall adds the platform-facing routes and does the crypto; you only say what to do at each step. See Concepts & lifecycle for where this sits in the bigger picture.

What the platform calls

mountInstall mounts up to three routes on your app: The two install routes are authenticated with a one-time provision token, not a session token. That token is signed by the platform, carries your app’s aud, and carries a jti that blocks replay. You never parse it by hand — mountInstall verifies it and, crucially, only lets the workspace come from the verified token. A caller can never provision or purge a workspace it wasn’t minted a token for.

Wire up the lifecycle

Here’s the whole thing, straight from the starter’s api/src/app.ts:
The verifier and cypher come from your config.ts — the verifier is bound to the platform’s Ed25519 public key and your manifest audience, and the cypher encrypts the secrets you store. The rest is four hooks. Let’s take them one at a time.

Store the key on provision

onProvision runs once, when the merchant installs. It receives the workspace and the freshly minted API key:
Do one thing here: encrypt the key and store it against the workspace. Never write it in plaintext — this is a live credential to a merchant’s data. The SDK’s cypher (createCypher) is AES-256-GCM keyed on your app’s own secret, so a stored key is useless to anyone who doesn’t hold that secret:
Later, when your app calls Sentralbee as this workspace, you decrypt it back: await cypher.decryptSecret(row.api_key_enc). See Calling the API.

The webhook secret

If your manifest declares webhooks.events, the platform also delivers a webhookSecret in the same call — the signing secret you’ll check deliveries against. Store it exactly the same way, encrypted, per workspace:
webhookSecret is absent for apps that don’t subscribe to events, which is why it’s optional. If you use webhooks, verify each delivery with that stored secret — see Webhooks.

Why the jti is consumed after your work

The provision token is one-time. A jti on it — a unique id — blocks replay: the first time you see a given jti you accept it, and you reject any repeat. That’s what consumeJti and releaseJti are:
Here’s the subtle part. “One-time” has a trap: if you mark the token spent, then your onProvision work fails — a locked database, a downstream hiccup — a naive design would strand the workspace forever. The platform retries, but it retries with the same token, which you’ve already burned. The merchant is now installed with no key stored, and there’s no way to recover. The SDK closes that gap. It consumes the jti only after your onProvision succeeds, and releases it if your hook throws. So a transient failure just means the platform’s retry works cleanly:
  1. Verify the token (validate only — the jti is not consumed yet).
  2. Reject early if apiKey is missing, before touching the jti.
  3. Consume the jti. If it’s already used, reply 409 — a replay.
  4. Run your onProvision. If it throws, call releaseJti and reply 500.
  5. On success, the jti stays consumed and the workspace is provisioned.
releaseJti is optional, but provide it. Without it, one transient error during provisioning permanently strands a workspace. The starter wires it up for you.

Purge on uninstall

onUninstall runs when the merchant removes your app. The API key you were given is being revoked, so hold onto nothing:
Delete the stored key, any webhook secret, any config, anything you keep for that workspace. It’s the same one-time-token flow as provision, so it’s safe to run exactly once. Leaving data behind is both a privacy problem and a support one — if the merchant reinstalls later, they get a clean provision, not stale state.

Optional: config from the embed

There’s a fourth hook, onConnect, for when your embed UI needs to send you app-specific config — an external account id the merchant types in, a toggle, a mapping. Unlike the two install routes, this one is session-authed: the merchant’s embed calls it with a session token, so again the workspace comes from the token, not the body.
Leave it out entirely if your app doesn’t need it — the route only mounts when you supply the hook.

Where to next

  • Concepts & lifecycle — the trust model this fits into.
  • The manifest — declare your scopes, and webhooks.events to receive a webhook secret.
  • Webhooks — verify the events you subscribe to with the delivered secret.
  • Calling the API — decrypt the stored key and act as the workspace.