OAuth
Authorization-code + PKCE — the merchant-install flow every app goes through.
Prebit is the authorization server. Your app's backend is the OAuth
client. The flow is standard authorization-code + PKCE (S256 only —
plain is rejected), with a merchant's browser as the user-agent.
Merchant browser Prebit (auth server) Your server
| 1. redirected to consent screen | |
|-----------------------------------> |
| 2. approves install | |
|-----------------------------------> |
| 3. redirect to your redirect_uri?code=...&state=... |
|---------------------------------------------------------->
| | 4. POST /oauth/token
| <----------------------
| | 5. access_token + refresh_token
| ----------------------->1. Build the authorization URL
import { generatePkcePair, buildAuthorizationUrl } from "@prebit/sdk";
const { codeVerifier, codeChallenge } = generatePkcePair();
const state = crypto.randomUUID();
const authorizeUrl = buildAuthorizationUrl({
baseUrl: "https://admin.prebit.in",
clientId: "...",
redirectUri: "https://your-app.example.com/oauth/callback",
scopes: ["read_products", "write_products"],
codeChallenge,
state,
});
// Store codeVerifier server-side (session/signed cookie/short-lived row
// keyed by `state`) — you need it again in step 3.| Param | Required | Notes |
|---|---|---|
client_id | yes | From your PartnerApp registration |
redirect_uri | yes | Must be an exact match to a registered redirect URI |
scope | yes | Space-separated. Capped to what your app's PartnerApp.requestedScopes declares |
code_challenge | yes | PKCE challenge (S256) |
code_challenge_method | yes | Must be S256 |
state | recommended | Echoed back verbatim on redirect; use for CSRF protection |
2. Merchant approves
The merchant (with ADMIN+ store access) approves on Prebit's own consent
screen. You don't implement this step.
3. Receive the authorization code
Prebit redirects to your redirect_uri:
GET https://your-app.example.com/oauth/callback?code=<opaque code>&state=<your state>The code is single-use and short-lived (5-minute TTL). Verify state
matches before proceeding.
4. Exchange the code for tokens
import { exchangeAuthorizationCode } from "@prebit/sdk";
const tokens = await exchangeAuthorizationCode({
baseUrl: "https://admin.prebit.in",
code,
codeVerifier,
redirectUri: "https://your-app.example.com/oauth/callback",
clientId: "...",
clientSecret: "...",
});Response:
{
"access_token": "...",
"refresh_token": "...",
"token_type": "bearer",
"expires_in": 900,
"scope": "read_products write_products",
"mode": "offline"
}scope is the granted set, which may be a subset of what you
requested — always trust this field.
Errors: 400 with error: "invalid_request" (missing fields), 400 with
error: "invalid_grant" (bad/expired/reused code, or PKCE mismatch),
401 with error: "invalid_client" (bad client id/secret).
5. Refresh an expired access token
import { refreshAccessToken } from "@prebit/sdk";
const tokens = await refreshAccessToken({
baseUrl: "https://admin.prebit.in",
refreshToken,
clientId: "...",
clientSecret: "...",
});Same response shape as step 4. Refresh tokens rotate — store the new one from every response.
Revoking a token
import { revokeToken } from "@prebit/sdk";
await revokeToken({ baseUrl: "https://admin.prebit.in", token, clientId: "...", clientSecret: "..." });Always resolves {"ok":true} for a well-formed request, even if the token
was already revoked or unknown (RFC 7009 semantics) — this is your
opt-in cleanup, distinct from the merchant clicking "Uninstall," which
revokes tokens automatically without you calling anything.
Redirect URI rules
Exact, pre-registered HTTPS URL — no wildcard or partial-path matching.
What happens on uninstall
When a merchant uninstalls your app, Prebit revokes all tokens
immediately, stops future webhook/job deliveries, deletes
Extension Storage, and cancels any active
billing:"prebit" subscription. Build your app so a 403 "Installation is not active" response is handled as "this merchant uninstalled," not
retried forever.
A full working example
prebit-examples/oauth-example is this entire flow,
end to end, in one runnable file — including the local callback server.