Skip to main content

Authentication

Three credentials, three jobs. Most confusion comes from using the wrong one.

1. Client ID and secret — identify your app​

Minted when you create the app. They identify the app, never a store, and they live on your server. The secret is shown once; Tanqory stores only a hash.

Rotating takes effect immediately, with no grace period — the reason to rotate is that the old secret should stop working now. Your client_id does not change, so stored integration config stays valid.

2. Store access token — call the API​

Exchanged from your credentials for one store:

POST https://api.tanqory.com/api/v1/apps/token
{ "grant_type": "client_credentials", "client_id": "…", "client_secret": "…", "store_id": "…" }

@tanqory/plugin-sdk does this for you, caches per store, and renews before expiry.

The token is capped to the scopes that merchant granted — not to what your manifest asks for. A merchant who approved two of your three requested scopes gets you a token with two.

There is no refresh token. Renewal re-runs the exchange, which re-checks that your app is still installed. That is deliberate: a merchant who uninstalls stops your access within minutes rather than whenever a long-lived refresh token happened to expire.

webhook_secret — the value that signs your deliveries​

The same response carries the secret Tanqory signs this store's webhooks with:

{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 900,
"scope": "orders.view products.view",
"webhook_secret": "whsec_…"
}

Three things about it that catch people out:

  • It is not your client secret. Your client secret authenticates you to the token endpoint and never signs anything. Verifying X-Tanqory-Signature against it always fails.
  • It is per installation, so per store. An app on two hundred stores has two hundred of these. Key whatever you cache by store_id.
  • It is absent when your app declares no webhooks. The secret is minted lazily on the first webhook sync, so the field simply does not appear until your manifest declares a topic.

It is returned only here. The embedded grant (/apps/embedded-token) never carries it, because that token is answered to a browser and a signing secret in a browser is a published secret.

3. Embedded session — inside the dashboard​

Handled entirely by useTanqoryApp(). Your embedded page never touches credentials: the dashboard is the token authority and hands one over the bridge, which is why the same page cannot be opened outside the dashboard and expected to work.

Errors you will see​

ResponseMeaningDo
401 on token exchangeBad credentials, or your app is not installed on that storeBoth answer identically on purpose — telling them apart would let anyone enumerate which apps exist and where they are installed
403 on an API callMissing scope, or the endpoint is not open to appsRequest the scope in your next manifest version. Do not retry
401 on an API callToken expired mid-flightThe SDK retries once, then reports. Usually means the merchant uninstalled