Getting Started
This guide creates an OAuth access token and uses it to open a hosted checkout session.
1. Provision OAuth client credentials
Create a test OAuth client in the Peak Gateway dashboard or ask your
organization administrator to provision one. Store the returned clientId and
one-time clientSecret in a server-side secret manager. Production uses a
separate client and secret.
Grant only the scopes your integration needs. The checkout request below needs
hosted-payments:write.
2. Mint an access token
Use the OAuth 2.0 client credentials grant from a trusted backend:
curl -sS -X POST "https://api.peakgateway.co/auth/oauth2/token" \
-u "${PEAK_CLIENT_ID}:${PEAK_CLIENT_SECRET}" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "scope=hosted-payments:write"
Read access_token and expires_in from the response. Never put the client
secret in browser or mobile code.
If your organization issued a Gateway API key instead, exchange it for a short-lived access token:
curl -sS -X POST "https://api.peakgateway.co/auth/oauth2/api-key/token" \
-H "X-Api-Key: ${PEAK_API_KEY}"
3. Create a checkout session
Amounts and line-item prices are integer minor units. This example creates a USD 49.99 checkout:
curl -sS -X POST "https://pay.peakgateway.co/api/v1/checkout/sessions" \
-H "Authorization: Bearer ${PEAK_ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ${CHECKOUT_CREATE_KEY}" \
-d '{
"amount": 4999,
"currency": "USD",
"description": "Starter plan",
"successUrl": "https://example.com/success",
"cancelUrl": "https://example.com/cancel",
"lineItems": [
{
"name": "Starter plan",
"quantity": 1,
"unitPrice": 4999
}
],
"locationId": "loc_123"
}'
Persist CHECKOUT_CREATE_KEY (nonblank, at most 64 characters), the location
and original request before dispatch. Recover a lost response with authenticated
GET /api/v1/checkout/sessions/by-key?idempotencyKey=order%2F123&locationId=loc_123.
An absent lookup result does not rule out a delayed original create; any retry
must keep the original key and body. Changed input under the same identity
returns HTTP 409.
Create replay and by-key lookup return the original persisted payment methods,
not current payment eligibility. Read the ordinary session surface for current
capabilities; historical lookup never grants another payment attempt. When no
return URLs were supplied, Gateway stores its canonical
/c/{sessionId}/success and /c/{sessionId}/cancel destinations rather than null.
Recovery consumers validate those destinations against the authenticated checkout
URL and original session identity, without including its bearer fragment.
currency is required. amount, return URLs, line items, and merchant/location
context depend on the checkout you are building; consult the generated request
schema before adding optional fields.
Line items follow two rules:
- A line is either a catalog line (
priceId, charged at the catalog price) or a custom line (nameplusunitPrice). SendingunitPricetogether withpriceIdreturns400. - When you send
lineItems, either omitamountso Gateway uses their subtotal as the base session amount, or send exactly that subtotal (sum ofquantity× unit price). Any otheramountreturns400. Discounts, shipping, and tax may change the final payable total. Without line items,amountis required.
4. Continue the payment flow
Redirect the shopper to the hosted checkout URL from the session response. The shopper-facing pay route uses the checkout session token, not your OAuth client secret. Confirm the final outcome through the session response and configured webhooks rather than trusting a browser redirect alone.
Payment errors and uncertain outcomes
HTTP status alone does not decide whether money moved. Inspect error.code.
The following codes have different recovery contracts:
| HTTP | error.code | Meaning and recovery |
|---|---|---|
| 409 | PAYMENT_IN_DOUBT | Unknown/pending, never a decline or an ordinary conflict. The original operation may still approve, refund, or void after the HTTP response. Keep the original operation identity and reconcile it. |
| 409 | IDEMPOTENCY_CONFLICT | The operation identity was reused with a different request. Do not silently replace its key or change its body. |
| 409 | IDEMPOTENCY_IN_PROGRESS | The original request is still reserved. Observe its owner; this is not proof that no payment happened. |
| 502 | UPSTREAM_NOT_EXECUTED | Gateway has explicit proof that this upstream call was not executed. This guarantee does not apply to other 5xx responses or transport failures. |
PAYMENT_IN_DOUBT responses use Cache-Control: no-store and the ordinary error
envelope (success: false, error: {code, message}). They are not final payment
receipts. Transaction sale/auth/force-auth (including Virtual Terminal and invoice),
capture/increment/void/refund/tip/total adjustment, wallet charge/authorization,
checkout payment submissions, gift redemption/refund and deterministic installment
charges recover through their durable owner rather than replaying an edge-cached timeout.
For transaction and wallet operations, retain the exact original request and
Idempotency-Key; an exact same-key replay recovers the original durable outcome
without authorizing a second provider dispatch. If the transaction ID is known,
read its transaction/operation status instead. For checkout payments, read the
original session and preserve its attempt identity. Gift-card recovery preserves
the original body idempotencyKey and card/effect identity; installment recovery
preserves the original plan, stage and attempt number. A still-unknown result must
remain pending; never create a fresh key or a new payment to recover a lost response.
An absent read result does not prove non-execution. Setup-intent verification and
other owner-specific flows retain their own confirmation/recovery contracts;
this is not permission to replay those mutations blindly.
Wallet sale/authorization replay also binds the original nonempty line items and
nonzero tax. Null/empty lines and null/zero tax are equivalent and retain their
historical fingerprint. When a historical operation has no retained commercial
proof, a request with commercial fields fails closed with IDEMPOTENCY_CONFLICT;
Gateway never guesses those fields or returns an approval for different details.
Recover the original status, not a new payment: with transactions:read,
query GET /api/v1/transactions?locationId=...&search=<URL-encoded-original-key>,
select only a record with the exact original external reference and expected
location/type, then read GET /api/v1/transactions/{transactionId}?locationId=....
Search is not exact identity proof and an absent search result is not proof of
non-execution. If needed, include the appropriate status filter for historical
EXPIRED_UNCONFIRMED records. Do not change the key to bypass a replay conflict.
Next steps
- Review Authentication for token and scope boundaries.
- Review the generated checkout-session and payment schemas.
- Configure and verify webhook signatures before processing async updates.
- Use an idempotency key on supported transaction, capture, refund, and void requests.