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-Signatureagainst 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
| Response | Meaning | Do |
|---|---|---|
401 on token exchange | Bad credentials, or your app is not installed on that store | Both answer identically on purpose — telling them apart would let anyone enumerate which apps exist and where they are installed |
403 on an API call | Missing scope, or the endpoint is not open to apps | Request the scope in your next manifest version. Do not retry |
401 on an API call | Token expired mid-flight | The SDK retries once, then reports. Usually means the merchant uninstalled |