Current version: 2026-08-26
SB Checkout API v1 documentation
Build server-side integrations with isolated TEST and LIVE credentials, server-owned prices, idempotent writes and signed asynchronous events.
Quickstart
Create a TEST key in the Checkout developer area, keep it only on your backend, then create a hosted session with a unique idempotency key.
- Configure the business and receiving currency in Business. Create a TEST store, product and active offer. Generate a TEST key for the same store with checkout.sessions:create, checkout.sessions:read and checkout.orders:read, checkout.payment_methods:read. An active store does not confirm charging availability.
- SB_CHECKOUT_HOST is the SB Checkout platform domain, not the merchant domain. Use the returned offerPublicToken and query /payment-methods for the currency and key environment. Keep secret keys on the server only.
- The example omits successUrl: use the returned checkoutUrl for the canonical page. A custom return route must exist and satisfy the destination policy. Confirm the result via the API or a signed webhook, never the redirect. Set recoveryConsent to ACCEPT only after the buyer expressly agrees.
curl --request POST 'https://SB_CHECKOUT_HOST/api/checkout/v1/sessions' \
--header 'Authorization: Bearer sbck_test_REDACTED' \
--header 'SB-Checkout-Version: 2026-08-26' \
--header 'Idempotency-Key: order_20260825_0001' \
--header 'Content-Type: application/json' \
--data '{
"offerPublicToken": "offer_public_token",
"quantity": 1,
"customer": {
"firstName": "Ada",
"lastName": "Lovelace",
"email": "ada@example.com",
"phone": "+351912345678",
"country": "PT"
},
"paymentMethodRequested": "SB_PAY",
"recoveryConsent": {
"version": "checkout-recovery-consent-v1",
"action": "DECLINE",
"channels": { "email": false, "sms": false, "whatsapp": false }
}
}'Hosted or custom checkout
Use hosted sessions when SB Checkout renders the purchase. Use Payment Intents when your site renders the experience. Both use server-owned prices, the same order source of truth, consented affiliate attribution and signed events.
Hosted checkout
Create a session on your backend and redirect the buyer to the returned checkout URL.
Custom checkout
Create a Payment Intent on your backend, then use only its origin-bound client secret with the browser SDK.
// Trusted merchant backend
const response = await fetch("https://SB_CHECKOUT_HOST/api/checkout/v1/payment-intents", {
method: "POST",
headers: {
Authorization: "Bearer sbck_test_REDACTED",
"SB-Checkout-Version": "2026-08-26",
"Idempotency-Key": "cart_8f31_payment_intent",
"Content-Type": "application/json",
},
body: JSON.stringify({
offerPublicToken: "offer_public_token",
customer: { firstName: "Ada", lastName: "Lovelace", email: "ada@example.com" },
allowedOrigins: ["https://shop.example.com"],
}),
});
if (!response.ok) {
throw new Error(`Payment Intent failed (request ${response.headers.get("X-Request-Id")})`);
}
const { paymentIntent } = await response.json();
// Return only paymentIntent.clientSecret to the exact allowed browser origin.
// Browser: after loading /checkout/sdk/v1/sb-checkout.js; never expose sbck_*.
const checkout = new SBCheckout({
clientSecret: paymentIntent.clientSecret,
apiBase: "https://SB_CHECKOUT_HOST",
});
const methods = await checkout.listPaymentMethods();
if (!methods.length) throw new Error("No payment methods are available");
const intent = await checkout.confirmPayment({
paymentMethod: methods[0].type,
idempotencyKey: SBCheckout.createIdempotencyKey("cart_8f31"),
});<script
src="https://SB_CHECKOUT_HOST/checkout/sdk/v1/sb-checkout.js"
integrity="sha384-WpLaTxPMdY5dPRTN1YDIFP3QH7pTtyo54r+aRKWjvphJc7Wpb+R3hhby8hCgGzPD"
crossorigin="anonymous"
></script>Contract and security
Authentication
Authorization: Bearer sbck_test_*
Client authentication
Origin-bound payment intent client_secret
Version
SB-Checkout-Version: 2026-08-26
Idempotency
Idempotency-Key on creating POST commands
Correlation
X-Request-Id on every response
Pagination
Query parameters: page, limit <= 100, search and status
Source of truth
Order state plus signed webhook events
Never expose a secret key in browser code, mobile applications, screenshots, logs or public repositories.
Resources
| Resource | Endpoints |
|---|---|
| Stores | GET /stores · POST /stores · GET/PATCH /stores/{id} |
| Products | GET /products · POST /products · GET/PATCH /products/{id} |
| Offers | GET /offers · POST /offers · GET/PATCH /offers/{id} |
| Customers | GET /customers · POST /customers · GET/PATCH /customers/{id} |
| Sessions | POST /sessions · GET /sessions/{id} |
| Orders | GET /orders · GET /orders/{orderTokenOrNumber} |
| Payment methods | GET /payment-methods |
| Payment intents | GET/POST /payment-intents · GET /payment-intents/{id} · POST /payment-intents/{id}/confirm · POST /payment-intents/{id}/cancel · POST /payment-intents/{id}/capture |
| Refunds | GET/POST /payment-intents/{id}/refunds · GET/POST /orders/{orderTokenOrNumber}/refunds |
| Disputes | POST /payment-intents/{id}/disputes · GET /disputes · POST /disputes/{id}/resolve |
| Settlements | GET /settlements |
| Payouts | POST /settlements/{id}/payouts |
| Payment links | GET /payment-links · POST /payment-links · GET /payment-links/{id} · POST /payment-links/{id}/deactivate |
| Webhooks | GET/POST /webhook-endpoints · GET/PATCH /webhook-endpoints/{id} · POST /webhook-endpoints/{id}/rotate-secret · GET /webhook-deliveries |
Payment methods
Discover available payment methods through the API. Availability depends on the currency, country and the payment methods enabled for the account.
SB Pay
Internal SB payment adapter. Lifecycle capabilities vary by TEST or LIVE mode and must be read from the descriptor.
Pix
Pix payments in BRL. Confirmation returns a DISPLAY_PIX_QR action; keep the same intent and poll until a terminal state.
Payment lifecycle
Capture, refund, dispute and payout commands are server-only, idempotent and capability-gated by credential mode and payment adapter.
| Operation | TEST | LIVE |
|---|---|---|
| Manual capture | Available | Adapter dependent |
| Full and partial refunds | Available | Adapter dependent |
| Dispute simulation and resolution | Available | Adapter dependent |
| Payout simulation | Available | Adapter dependent |
The current SB Pay LIVE adapter advertises none of these lifecycle commands. Unsupported requests return HTTP 422 with a stable capability code before financial state changes.
// Trusted merchant backend only: never expose sbck_* in the browser.
const response = await fetch(
"https://SB_CHECKOUT_HOST/api/checkout/v1/payment-intents/sbpi_test_ID/refunds",
{
method: "POST",
headers: {
Authorization: "Bearer sbck_test_REDACTED",
"SB-Checkout-Version": "2026-08-26",
"Idempotency-Key": "refund_order_1042_attempt_1",
"Content-Type": "application/json",
},
body: JSON.stringify({ amount: "10.50", reason: "customer_request" }),
}
);
const body = await response.json();
if (!response.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
console.log(body.refund.id, body.refund.status);Webhooks
Verify the exact raw request bytes, reject stale timestamps and deduplicate by event.id. During secret rotation, accept current and previous signatures only for the declared overlap window.
import crypto from "node:crypto";
export function verify(rawBody, timestamp, signature, secret) {
if (!(Buffer.isBuffer(rawBody) || typeof rawBody === "string")) return false;
if (typeof timestamp !== "string" || !/^(?:\d{10}|\d{13})$/.test(timestamp)) return false;
if (typeof signature !== "string" || signature.length > 2048) return false;
if (typeof secret !== "string" || !secret) return false;
// Current wire format: Unix milliseconds. Accept legacy Unix seconds too.
// Keep the original timestamp text unchanged in the signed input.
const sentAt = Number(timestamp) * (timestamp.length === 10 ? 1000 : 1);
if (Math.abs(Date.now() - sentAt) > 300_000) return false;
const parts = signature.split(",").map((part) => part.trim());
if (parts.length > 16) return false;
const signatures = parts.filter((part) => part.startsWith("v1="));
if (!signatures.length || signatures.some((part) => !/^v1=[a-fA-F0-9]{64}$/.test(part))) return false;
const expected = crypto.createHmac("sha256", secret)
.update(timestamp + ".", "utf8")
.update(rawBody) // raw Buffer from the HTTP body, before JSON parsing
.digest();
return signatures.some((part) => crypto.timingSafeEqual(
expected, Buffer.from(part.slice(3), "hex")
));
}Errors and retries
202processing or ambiguous provider result; retrieve the same Payment Intent and do not create a second attempt400invalid request or unsupported API version401/403invalid credential, mode or missing scope409idempotency conflict or stale optimistic version429rate limited; honor Retry-After with exponential backoff and jitter422operation not supported by the selected payment adapter or payout rail5xxtemporary server failure; retry only idempotent requests
For PATCH requests, send ifVersion from the latest resource response. A stale version returns HTTP 409 and never overwrites the newer change.