Skip to main content
Salesive apps authorize with the OAuth 2.1 authorization code flow and PKCE is mandatory (S256). This page walks through the full handshake: redirecting the merchant to consent, receiving an authorization code, and exchanging it for tokens.

Endpoints

Salesive publishes an OAuth 2.1 discovery document. Fetch it once and read the endpoints from it rather than hardcoding paths:
Embedded apps can also use a dashboard session token and the token exchange grant instead of handling the install callback. The token pair is the same.
The authorization_endpoint is on the Salesive dashboard host (app.salesive.com) — that’s where the merchant approves. The token_endpoint and all API calls are on the API host (api.salesive.com). Always read these from the discovery document rather than hardcoding.
You’ll need your client_id and client_secret from the Developer console (Apps → Developer). See Build & publish.

How an install starts

Every install ends at the same consent screen, but there are two ways to reach it. From the marketplace. The merchant opens your listing under Apps and clicks Install. The dashboard starts the install for the store they picked:
The response is a consent ticket plus the OAuth values the consent screen needs:
That call creates no installation. The ticket is single use, expires after 10 minutes, and is bound to your app, the store and the merchant who pressed Install. The dashboard then runs the consent step for you:
  1. It opens the consent screen with GET /oauth/authorize?ticket=.... That call validates the ticket without consuming it and returns the app plus the scopes that will actually be granted.
  2. The merchant approves. The dashboard calls POST /oauth/authorize with approved: true and the ticket. That call consumes the ticket once, creates the installation, and returns a redirect_url: your registered redirect URI with an authorization code and the state.
  3. The dashboard navigates the merchant’s browser to that URL in the same tab. Your app’s callback receives the code.
The state is the value from the consent ticket, so your callback may not have a pending OAuth state for it. Treat a marketplace callback as an install callback rather than a replay of an authorize request your app started.

The PKCE verifier arrives in the fragment

The code is bound to the PKCE challenge the dashboard sent to the consent screen, so the token exchange needs the matching verifier. The dashboard appends it to your callback as a URL fragment:
Your callback page reads it from location.hash and sends it with the token exchange:
The query string and the registered redirect URI are unchanged, so the redirect_uri you send to the token endpoint is still exactly the URI you registered. A browser never sends a fragment to a server, a log or a referrer, so the verifier stays out of server access logs and Referer headers. Do not copy it into your own logs, analytics or error reports either. This covers both marketplace paths: the verifier from the consent ticket, and the fallback where the dashboard generates the PKCE pair itself when the ticket endpoint is unavailable. Either way the verifier is in the fragment. An install your app started itself through the authorization endpoint does not need this: in that flow the verifier is yours and you already hold it. Your app exchanges that code, with the verifier from the fragment, as in Step 3 below. The installation already exists by the time your callback runs, so do not run the authorize step again when the app opens. The token pair you store at install is the pair the app refreshes from then on. After the exchange, take the merchant somewhere useful: return them to the dashboard (for example https://app.salesive.com/apps/<slug>, the app’s page, or https://app.salesive.com/apps/installed) or launch your app’s own UI in the same tab. Do not leave them on a blank callback page. If your app is embedded, the dashboard can launch it from the installed list; it uses the tokens you stored at install. If the redirect URL is missing or is not an absolute https URL, the dashboard falls back to the app’s page and shows a toast. The installation is still created, and your app can get tokens with the token exchange grant when it opens. From your own install link. Send the merchant to the authorization endpoint with the normal OAuth parameters below. The same consent screen appears, and approving it creates the installation and issues the code. This is the path an app’s own install page uses, and it is unchanged.
There is no direct install endpoint. POST /api/v1/apps/marketplace/:slug/install no longer exists, and a ticket that is missing, expired or already used is refused with 400 invalid_ticket. Nothing is installed without the approval step.
The consent screen shows only the scopes Salesive will actually grant. A scope your app is not allowed to hold is dropped, and both install-consent and GET /oauth/authorize explain every drop in deniedScopes. See Scopes & permissions.

Prerequisites

  • A registered app with a client_id, client_secret, and at least one redirect URI.
  • A server endpoint that can receive the OAuth callback and hold the client_secret.
  • The scopes your app needs — see Scopes & permissions.

Step 1 · Generate a PKCE pair

For each install attempt, generate a random code_verifier and derive its code_challenge. Keep the verifier server-side (or in the session) — you’ll need it at token exchange.
PKCE is required. Requests without a valid code_challenge (method S256) are rejected.
Send the merchant’s browser to the authorization_endpoint with your request parameters. The merchant, who is signed in to Salesive, sees a consent screen listing the app, the store and the scopes that will be granted, then approves. A marketplace install also carries the ticket from How an install starts, which supplies the parameters below.
On approval, Salesive consumes the ticket (when one is present) and creates the installation on the merchant’s active store. It then redirects back to your redirect_uri with a code (and your state). A marketplace install follows the same redirect; the dashboard performs it, as described above. On denial, it redirects with ?error=access_denied and burns the ticket so it cannot be replayed.
The token response’s scope is the authoritative list of what was granted. It can be shorter than what you asked for when a scope is not available to your app; see Scopes & permissions.

Step 3 · Exchange the code for tokens

From your server, POST to the token_endpoint. Send your client_secret and the PKCE code_verifier you stored in Step 1.
Store the refresh_token securely (encrypted at rest), keyed to the merchant/store. The access_token is short-lived (one hour).
Client credentials may also be sent as HTTP Basic auth (Authorization: Basic base64(client_id:client_secret)) instead of in the body.

Step 3b · Bind your session to the authenticated user

The token response does not include a user id — the access_token is scoped to the installation (store + app), not to an individual Salesive user. This matters when multiple people share access to the same store: if you only bind your session cookie to the store id, a second person who opens the app in the same browser inherits the first person’s session. The fix: immediately after persistTokens, call the default-granted GET /api/v1/app/context endpoint using the token you just stored and embed the returned user.id into your session cookie.
Then, when the Salesive dashboard opens your iframe it appends ?user=<id> to the URL. Read that value and compare it against the session’s stored userId on every request. A mismatch (different Salesive account switched in) means the session is stale — clear the cookie and return authenticated: false so the gate prompts the new user to re-authenticate.
The ?user= value is not a trust signal — it is only used to look up and invalidate the right cookie. The actual authentication proof is always the signed, HttpOnly session cookie.
The Salesive App Starter implements this pattern out of the box in server/session.js (token format), server/routes/oauth.js (userId capture), and server/middleware/requireShop.js (per-request validation). If you copied an earlier version of the starter, update those three files.

Step 4 · Call the API

Send the access token as a bearer token. You do not need to specify the store — the token is already bound to the store it was installed on.
See Scopes & permissions for the full list of endpoints your app can reach and the scope each one requires.

Step 5 · Refresh the access token

When the access token expires, exchange your refresh token for a new pair. Refresh tokens rotate — the old refresh token is invalidated atomically and you receive a new one, so always persist the latest. A refresh token that has already been rotated answers invalid_grant, including when two refreshes race: only one of them can win. The new pair carries the scopes the installation holds at that moment. They are the same scopes the pair was issued with unless the merchant re-consented to a different set in between, in which case the re-consented set wins.

Handling uninstalls & revocation

A merchant can uninstall your app at any time from Apps → Installed apps in their dashboard. When they do, the installation is deactivated and all of your app’s tokens for that store stop working immediately — even an access token that hasn’t expired yet. The same happens on every store at once if you delete the app from the developer console, or if Salesive suspends it. Treat a 401 Unauthorized with a message like “This app is no longer installed on the store” as an uninstall signal: stop calling the API for that store and clean up your stored tokens. You can also proactively revoke a token:

Common errors

Next steps

Scopes & permissions

Map each scope to the endpoints it unlocks.

Build & publish

Register, list, and submit your app for review.