Skip to main content

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 (name plus unitPrice). Sending unitPrice together with priceId returns 400.
  • When you send lineItems, either omit amount so Gateway uses their subtotal as the base session amount, or send exactly that subtotal (sum of quantity × unit price). Any other amount returns 400. Discounts, shipping, and tax may change the final payable total. Without line items, amount is 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:

HTTPerror.codeMeaning and recovery
409PAYMENT_IN_DOUBTUnknown/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.
409IDEMPOTENCY_CONFLICTThe operation identity was reused with a different request. Do not silently replace its key or change its body.
409IDEMPOTENCY_IN_PROGRESSThe original request is still reserved. Observe its owner; this is not proof that no payment happened.
502UPSTREAM_NOT_EXECUTEDGateway 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.