Lock product version, pricing, and currency for checkout
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:
Merchant creates a checkout session (server-side)
Returns checkoutUrl to the frontend
Consumer clicks the link and lands on the hosted checkout page
Consumer fills in billing details, previews tax, and completes the order
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.
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, billingDetailEmail 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.
Retry policy: Never retry 4xx — fix the request and resubmit. Retry 5xx with exponential backoff (start 5s, max 3 attempts).
Status
errors[0].message
What it means
Recommended handling
400
Missing or invalid header: x-context-environment
Environment header missing or not test / prod
Fix the header, resubmit
400
Invalid JSON body
Request body is not valid JSON
Fix the body, resubmit
400
Missing required fields: productId, currency
productId or currency is missing
Fix the body, resubmit
400
Invalid 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
400
Invalid billingDetail
Billing detail validation failed (e.g. missing state for US)
Fix billing detail, resubmit
400
Expected format: PROD_xxx, got "..."
productId Short ID could not be decoded
Fix productId format, resubmit
400
orderMerchantExternalId must not be empty
Field was provided but is empty after trim
Omit the field or pass a non-empty value
400
orderMerchantExternalId must be at most 128 characters
Value exceeds 128 chars
Shorten to 128 chars or less
400
Currency X is not supported for this product
Product has no price snapshot for the requested currency
Use a currency supported by the product
401
Unauthorized
Invalid API Key signature or Store Slug
Verify auth headers
403
Store is not active
Store status is not active
Activate the store first
403
Store is not approved for production payments
Store is not enabled for prod environment
Use test environment, or get the store approved
403
Product does not belong to this store
Product is not owned by the visiting store
Verify the product/store pair
404
Store not found
No store matches the given slug
Verify slug and environment
404
Product not found
Product does not exist
Verify the productId
404
Product not found or not active for this environment
Product has no active version for the given environment
Activate a product version for this environment
500
Internal server error
Unexpected server-side failure
Retry 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.