Authentication
How to authenticate to the PaymentGateway API — secret keys, OAuth bearer tokens, test/live mode, and required headers.
PaymentGateway supports three ways to authenticate, depending on what you're
building. All of them are sent in the Authorization header.
| Method | Use case | privilege |
|---|---|---|
| Secret key (HTTP Basic) | Server-to-server integrations | machine |
| OAuth 2.0 bearer token | Dashboard / user-facing apps | user / superuser |
| One-time session | Hosted checkout & anonymous pay | — |
Secret key (server-to-server)
Examples below use
$WALLETfor the API base —{base_url}/api/v1/{app}/platform/wallet(e.g.http://localhost:5002/api/v1/app1/platform/wallet). See Quick guide.
Send your secret key as the username in HTTP Basic auth, with an empty password:
curl "$WALLET/charges" \
-u "${SECRET_KEY}:"Equivalently, build the header yourself:
const auth = "Basic " + btoa(`${SECRET_KEY}:`);
// Authorization: Basic c2tfdGVzdF8uLi46Keep secret keys server-side only — never ship them in browser or mobile code. For client-side payment collection, use the publishable key and the checkout SDK (see the public-key model).
OAuth 2.0 bearer token (user apps)
User-facing apps (the dashboard and admin panels) authenticate end users through Ory — Kratos for identity/login and Hydra as the OAuth 2.0 server — then call the API with the resulting access token:
curl "$WALLET/oauth/me" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"The high-level flow is: redirect to Hydra's /oauth2/auth → user logs in via
Kratos → Hydra returns an authorization code to your callback → exchange the
code at POST /oauth/token for an access + refresh token.
Token lifetimes
| Token | Lifetime |
|---|---|
| Access token | 12 hours |
| Refresh token | 30 days |
| Authorization code | 10 minutes |
Refresh before the access token expires (e.g. every 11 hours) via
POST /oauth/token/refresh. Refresh tokens may be rotated on each use — always
store the newly returned refresh token.
One-time session (checkout)
Hosted checkout and anonymous "pay" links use a short-lived one-time session so a buyer can complete a single payment without a full account. The checkout SDK manages this session for you — you do not handle the credential directly. See the Checkout SDK guides for integration details.
Required request headers
| Header | When | Purpose |
|---|---|---|
Authorization | Always | Basic … (secret key) or Bearer … (access token) |
X-Test-Mode | Optional | true for sandbox, omit/false for live — see below |
X-Country | Some endpoints | Two-letter country code (e.g. TH) for multi-country apps |
Content-Type | POST / PATCH | application/json |
Test vs live mode
Test and live data are stored in separate database schemas
(wallet_test / wallet_live), so test activity can never touch live money.
Select the schema per request with the X-Test-Mode header:
curl "$WALLET/charges" \
-u "${SECRET_KEY}:" \
-H "X-Test-Mode: true"Every response includes a livemode boolean reflecting the schema the data came
from, so you can always verify which environment you hit:
{ "id": "chg_123", "object": "charge", "livemode": false }Public vs secret keys
PaymentGateway uses a session-driven key model rather than hardcoded client keys: the secret key authenticates server-side calls, while client-side payment collection (hosted checkout or the embeddable SDK) is driven by a checkout session and the publishable key — no secret material in the browser. See the checkout SDK guides for the full integration.
Common authentication errors
These arrive in the standard error envelope:
| Code | HTTP | Meaning |
|---|---|---|
E1200 | 401 | Authentication required |
E1210 | 401 | Invalid bearer token |
E1211 | 401 | Invalid or inactive secret key |
E1212 | 401 | Invalid one-time session |
E1213 | 401 | Missing authentication headers |
E1300 | 403 | Insufficient permissions |
E1303 / E1304 | 403 | KYC / KYB verification required |
On 401, refresh your token (bearer flow) or re-check your key; on repeated
failure, restart the login flow.