Skip to main content
Create a checkout session that locks product version, pricing, and currency. This is the first step in the checkout flow for both one-time and subscription products. The typical flow is:
  1. Merchant creates a checkout session (server-side)
  2. Returns checkoutUrl to the frontend
  3. Consumer clicks the link and lands on the hosted checkout page
  4. Consumer fills in billing details, previews tax, and completes the order
POST /v1/actions/checkout/create-session
Authentication: API Key or Store Slug

Request Body (API Key)

FieldTypeRequiredDescription
productIdstringYesProduct ID in Short ID format (PROD_xxx)
currencystringYesISO 4217 currency code (e.g. USD, EUR, JPY)
priceSnapshotobjectNoOverride pricing at session creation (API Key only, see below)
withTrialbooleanNoEnable trial period (subscription products only)
buyerEmailstringNoPre-fill consumer’s email on the checkout page
billingDetailobjectNoPre-fill billing details (see below)
successUrlstringNoOverride redirect URL after successful payment
expiresInSecondsnumberNoSession TTL in seconds (default: 2700 = 45 minutes, API Key only)
darkModebooleanNoEnable dark mode for the checkout page
metadataobjectNoCustom key-value pairs attached to the session
orderMerchantExternalIdstringNoYour business-side order reference (max 128 chars).

Request Body (Store Slug)

FieldTypeRequiredDescription
productIdstringYesProduct ID in Short ID format (PROD_xxx)
currencystringYesISO 4217 currency code
buyerEmailstringNoPre-fill consumer’s email
billingDetailobjectNoPre-fill billing details (see below)
successUrlstringNoOverride redirect URL after successful payment
darkModebooleanNoEnable dark mode for the checkout page
Store Slug authentication does not support priceSnapshot, expiresInSeconds, metadata, orderMerchantExternalId, or withTrial. These fields are silently ignored to prevent price tampering and session manipulation from the client side.
ID-naming convention: the same flat dual-key names (orderMerchantExternalId on checkout-side, refundTicketMerchantExternalId on refund-ticket-side) are used across request bodies, webhook payloads, and every GraphQL type that carries the value — so a value written at checkout can be read back from Order, Payment, Refund, or a webhook payload under the same field name.

Price Snapshot Object

FieldTypeRequiredDescription
amountstringYesPrice as a display format string (e.g., “29.00” = $29.00)
taxIncludedbooleanYesWhether the amount already includes tax
taxCategorystringYesTax category for tax calculation (e.g., saas, digital_goods)

Billing Detail Object

FieldTypeRequiredDescription
countrystringYesISO 3166-1 alpha-2 country code (e.g. US, JP, DE)
isBusinessbooleanYesWhether this is a business purchase
postcodestringNoPostal or ZIP code
statestringConditionalRequired for US and CA (e.g. CA, NY, ON)
businessNamestringConditionalRequired when isBusiness is true
taxIdstringConditionalRequired for EU countries when isBusiness is true (VAT number)

Session Locks

When a checkout session is created, the following values are locked and cannot change during the session lifetime: productVersionId, productName, priceInfo, storeName, billingPeriod, withTrial, theme, buyerEmail, billingDetail Email normalization: All email fields (email, buyerEmail, contactEmail) are normalized server-side via trim().toLowerCase() before storage, cache writes, and downstream calls. Foo@Bar.COM and foo@bar.com are treated as the same account.

Example Request

import { WaffoPancake } from "@waffo/pancake-ts";

const client = new WaffoPancake({
  merchantId: process.env.WAFFO_MERCHANT_ID!,
  privateKey: process.env.WAFFO_PRIVATE_KEY!,
});

const session = await client.checkout.createSession({
  productId: "PROD_7J3K5L8M2N4P6Q9R",
  currency: "USD",
  buyerEmail: "customer@example.com",
  successUrl: "https://example.com/thank-you",
});

console.log(session.sessionId);   // "cs_550e8400-e29b-41d4-a716-446655440000"
console.log(session.checkoutUrl); // "https://checkout.waffo.ai/..."
console.log(session.expiresAt);   // "2026-01-22T10:30:00.000Z"

Success Response (200)

{
  "data": {
    "sessionId": "cs_550e8400-e29b-41d4-a716-446655440000",
    "checkoutUrl": "https://checkout.waffo.ai/my-store-abc123/checkout/cs_550e8400-e29b-41d4-a716-446655440000",
    "expiresAt": "2026-01-22T10:30:00.000Z"
  }
}

Response Fields

FieldTypeDescription
sessionIdstringCheckout session ID (cs_ + UUID format, not a Short ID)
checkoutUrlstringFull URL to redirect the consumer to the hosted checkout page
expiresAtstringISO 8601 timestamp when the session expires

Errors

Retry policy: Never retry 4xx — fix the request and resubmit. Retry 5xx with exponential backoff (start 5s, max 3 attempts).
Statuserrors[0].messageWhat it meansRecommended handling
400Missing or invalid header: x-context-environmentEnvironment header missing or not test / prodFix the header, resubmit
400Invalid JSON bodyRequest body is not valid JSONFix the body, resubmit
400Missing required fields: productId, currencyproductId or currency is missingFix the body, resubmit
400Invalid currency format: "X". Must be 3 uppercase letters (e.g., "USD", "EUR", "JPY")Currency is not 3 uppercase letters (ISO 4217)Use a valid ISO 4217 code, resubmit
400Invalid billingDetailBilling detail validation failed (e.g. missing state for US)Fix billing detail, resubmit
400Expected format: PROD_xxx, got "..."productId Short ID could not be decodedFix productId format, resubmit
400orderMerchantExternalId must not be emptyField was provided but is empty after trimOmit the field or pass a non-empty value
400orderMerchantExternalId must be at most 128 charactersValue exceeds 128 charsShorten to 128 chars or less
400Currency X is not supported for this productProduct has no price snapshot for the requested currencyUse a currency supported by the product
401UnauthorizedInvalid API Key signature or Store SlugVerify auth headers
403Store is not activeStore status is not activeActivate the store first
403Store is not approved for production paymentsStore is not enabled for prod environmentUse test environment, or get the store approved
403Product does not belong to this storeProduct is not owned by the visiting storeVerify the product/store pair
404Store not foundNo store matches the given slugVerify slug and environment
404Product not foundProduct does not existVerify the productId
404Product not found or not active for this environmentProduct has no active version for the given environmentActivate a product version for this environment
500Internal server errorUnexpected server-side failureRetry with exponential backoff (start 5s, max 3 attempts)
The default session TTL is 45 minutes (2700 seconds). When using API Key auth, you can customize this with expiresInSeconds. Sessions lock the product version and pricing at creation time, so price changes will not affect existing sessions.