Skip to content

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

  1. Your app sends the store owner to the dashboard page /oauth/authorize with client_id, redirect_uri, scope, state, code_challenge and code_challenge_method=S256.
  2. The owner approves. Hekana records the installation with exactly the scopes requested, and redirects to your redirect_uri with a single-use code (valid 60 seconds) and your state. If the owner denies, you receive error=access_denied.
  3. Your server calls POST /oauth/token with the code and your PKCE verifier.
  4. You call /apps-api/* with Authorization: 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

LifetimeNotes
Authorization code60 secondsSingle use. A second exchange fails.
Access token1 hourOpaque string. Sent as a Bearer token.
Refresh tokenRotates on every use; the chain ends 30 days after the first grantReusing an already-rotated refresh token revokes the whole chain.

Refresh with POST /oauth/token:

POST /oauth/token (refresh)JSON
{
  "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:

ScopeAllows
read_ordersGET /orders; webhook topics orders/*
write_ordersPOST /orders
read_productsGET /products, GET /products/:id; webhook topics products/*
write_productsAccepted at registration. No endpoint uses it yet.
read_customersGET /customers, GET /customers/:id; webhook topic customers/created
read_analyticsGET /analytics
write_discountsPOST /discounts
app_data/data key-value store for the installation
send_messagesPOST /messages
checkout_extensions/checkout-extensions
billingPOST /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.

Next steps