Authentication and scopes
How a Hekana app authenticates: the OAuth authorization-code flow with PKCE, access and refresh token lifetimes, secret rotation and what each scope allows.
On this page
Apps authenticate with OAuth 2.0 authorization code plus PKCE. The result is a Bearer token tied to one installation of your app on one store.
The flow
- Your app sends the store owner to the dashboard page
/oauth/authorizewithclient_id,redirect_uri,scope,state,code_challengeandcode_challenge_method=S256. - The owner approves. Hekana records the installation with exactly the scopes requested, and redirects to your
redirect_uriwith a single-usecode(valid 60 seconds) and yourstate. If the owner denies, you receiveerror=access_denied. - Your server calls
POST /oauth/tokenwith the code and your PKCE verifier. - You call
/apps-api/*withAuthorization: Bearer <access token>.
Checks the authorize step makes: the app is approved, redirect_uri is one of the registered addresses (exact match), the challenge method is S256, and every requested scope was registered with the app. Only a store owner or admin can approve.
Tokens
| Lifetime | Notes | |
|---|---|---|
| Authorization code | 60 seconds | Single use. A second exchange fails. |
| Access token | 1 hour | Opaque string. Sent as a Bearer token. |
| Refresh token | Rotates on every use; the chain ends 30 days after the first grant | Reusing an already-rotated refresh token revokes the whole chain. |
Refresh with POST /oauth/token:
{
"grant_type": "refresh_token",
"refresh_token": "<the latest refresh token>",
"client_id": "<your client id>",
"client_secret": "<your client secret>"
}Save the new refresh token from every reply and discard the old one. After 30 days the owner must approve again. Confidential apps must send client_secret; public apps rely on PKCE only. Wrong or missing credentials answer 401 invalid_client; a bad code, verifier or refresh token answers 400 invalid_grant.
A refreshed token carries only the scopes the store currently grants, so a scope you ask for later does nothing until the owner approves it again.
When tokens stop working
Calls answer 401 invalid_token when the token is expired, revoked or unknown, when the store uninstalled, and when the app is not approved any more. Suspending an app cuts off every installation at once, and refresh stops too.
Changing scopes or redirect URIs on an approved app sends it back to review, and while it is in review its tokens do not work. Renaming the app or editing its listing does not. Plan scope changes as a new version and expect a pause.
Secrets
POST /oauth/apps/:id/rotate-secret returns a new client secret once and invalidates the old one immediately. Tokens already issued keep working until they expire. Public apps have no secret to rotate.
Scopes
Register only what you use. Each scope maps to endpoints:
| Scope | Allows |
|---|---|
read_orders | GET /orders; webhook topics orders/* |
write_orders | POST /orders |
read_products | GET /products, GET /products/:id; webhook topics products/* |
write_products | Accepted at registration. No endpoint uses it yet. |
read_customers | GET /customers, GET /customers/:id; webhook topic customers/created |
read_analytics | GET /analytics |
write_discounts | POST /discounts |
app_data | /data key-value store for the installation |
send_messages | POST /messages |
checkout_extensions | /checkout-extensions |
billing | POST /charge, GET /earnings. Spends the store's wallet; the highest-trust scope. |
A call without the scope answers 403 with insufficient_scope: <scope>. The webhook endpoints need no scope, but each topic needs the scope in the table above, and app/uninstalled needs none.
Embedded apps
An app can also be opened inside the dashboard in a frame. The dashboard passes the app a session token that lives 120 seconds and works once; the app's server redeems it with POST /oauth/session-token/verify (client_id, client_secret, session_token) and receives the store and the granted scopes. The address that is framed cannot be set through the developer API, so this is not self-serve yet.