Checkout Sessions
Checkout Sessions
Checkout sessions let you create a hosted page with a shareable URL. You create a session, then redirect your customer to the generated URL — or email it to them — to collect a payment, save a payment method, or start a subscription.
Choosing a mode
Set mode when you create the session. It decides what the page collects and what it promises the customer:
PAYMENT— collects a one-time payment.amountandcurrencyare required. Completes with acheckout_session.paidwebhook, sent once the underlying payment intent succeeds — immediately for a card, after the collection clears for a Bacs Direct Debit rail. This is the default.SETUP— saves a payment method for later and charges nothing. Omitamount; acustomeris required, and the saved method is bound to it permanently. Completes with acheckout_session.setup_succeededwebhook.SUBSCRIPTION— saves a reusable payment method, then creates a subscription or replaces an existing subscription's default payment method. Supply exactly one ofsubscription_dataorsubscription. Subscription invoices are collected separately; checkout completion does not mean an invoice was paid.
For a one-time payment and saving the card, usePAYMENTmode. TheSETUPpage tells the customer "you won't be charged now", and that promise isn't configurable — so don't reach forSETUPwhen you intend to charge immediately. Set acustomeron aPAYMENT-mode session and the page offers to save the card for future use; when the customer opts in, the method is stored against that customer and you can charge it later server-side. Without acustomeron the session, no save option is shown.
When a PAYMENT session has a customer, the Card option also offers compatible cards already saved to that customer. The newest usable card is selected by default, expired cards are shown but cannot be selected, and the customer can always choose Use a new card. The JS SDK handles 3D Secure authentication when required for the new payment; you do not need to list or pass saved payment methods to the hosted page yourself.
Use SETUP to save a method without creating a subscription — for example, a Direct Debit mandate ahead of billing, or a card before the first order. Use SUBSCRIPTION to create the subscription automatically when the customer completes checkout, including a subscription with a free trial.
Offering Apple Pay and Google Pay
A PAYMENT-mode page can offer Apple Pay and Google Pay alongside the card form. Both need enabling on your account first. See Digital Wallets for more information.
Checkout session lifecycle
- Create a checkout session — a hosted page URL is generated
- Redirect (or email) the customer to the URL
- The customer completes the page — paying in
PAYMENTmode, saving payment details inSETUPmode, or accepting subscription signup or payment-method replacement inSUBSCRIPTIONmode - The session transitions to
COMPLETEand the corresponding completion webhook is sent
Sessions expire after 24 hours by default. Subscription signup links can expire earlier at the trial end or scheduled start; use the returned expire_time.
Creating a checkout session
The Idempotency-Key header lets you safely retry the request if it fails — see Idempotency.
mode is omitted below, so this session is PAYMENT mode.
curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/checkout-sessions \
-H "Authorization: Api-Key $API_KEY" \
-H "Idempotency-Key: YOUR_UNIQUE_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": "10000",
"currency": "GBP",
"client_reference_id": "order_12345",
"success_url": "https://example.com/success",
"cancel_url": "https://example.com/cancel"
}'Response
{
"name": "accounts/acct_YOUR_ACCOUNT/checkout-sessions/cs_SESSION_ID",
"state": "OPEN",
"amount": "10000",
"currency": "GBP",
"client_reference_id": "order_12345",
"url": "https://pay.synaptopay.com/checkout?s_checkout_session=cs_SESSION_ID",
"payment_intent": "accounts/acct_YOUR_ACCOUNT/payment-intents/pi_PAYMENT_INTENT_ID",
"paid": false,
"success_url": "https://example.com/success",
"cancel_url": "https://example.com/cancel",
"expire_time": "2025-11-18T16:00:00Z",
"create_time": "2025-11-17T16:00:00Z",
"update_time": "2025-11-17T16:00:00Z"
}Redirect your customer to the url to complete payment.
Saving a payment method without charging
Pass mode: SETUP and a customer, and omit amount. currency is optional here — when set it restricts the page to rails supporting it; when omitted, every setup-capable rail on your account is offered.
curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/checkout-sessions \
-H "Authorization: Api-Key $API_KEY" \
-H "Idempotency-Key: YOUR_UNIQUE_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "SETUP",
"customer": "accounts/acct_YOUR_ACCOUNT/customers/cus_CUSTOMER_ID",
"metadata": { "plan_id": "plan_monthly" },
"success_url": "https://example.com/details-saved"
}'{
"name": "accounts/acct_YOUR_ACCOUNT/checkout-sessions/cs_SESSION_ID",
"mode": "SETUP",
"state": "OPEN",
"customer": "accounts/acct_YOUR_ACCOUNT/customers/cus_CUSTOMER_ID",
"setup_intent": "accounts/acct_YOUR_ACCOUNT/setup-intents/seti_SETUP_INTENT_ID",
"url": "https://pay.synaptopay.com/checkout?s_checkout_session=cs_SESSION_ID",
"paid": false,
"metadata": { "plan_id": "plan_monthly" },
"success_url": "https://example.com/details-saved",
"expire_time": "2025-11-18T16:00:00Z",
"create_time": "2025-11-17T16:00:00Z"
}paid stays false for the whole life of a SETUP session — nothing was charged. The saved method is attached to customer and cannot later be used for a different one.
Reconciling a completed setup
Listen for checkout_session.setup_succeeded, not setup_intent.succeeded. Only the session event carries the metadata you set at create time, so it's the one you can match back to the order, plan or booking that prompted the setup:
{
"name": "accounts/acct_YOUR_ACCOUNT/events/evt_EVENT_ID",
"type": "checkout_session.setup_succeeded",
"checkout_session": {
"name": "accounts/acct_YOUR_ACCOUNT/checkout-sessions/cs_SESSION_ID",
"mode": "SETUP",
"state": "COMPLETE",
"customer": "accounts/acct_YOUR_ACCOUNT/customers/cus_CUSTOMER_ID",
"setup_intent": "accounts/acct_YOUR_ACCOUNT/setup-intents/seti_SETUP_INTENT_ID",
"paid": false,
"metadata": { "plan_id": "plan_monthly" }
},
"event_time": "2025-11-17T16:04:00Z"
}Read the setup_intent from the event to get the payment_method it saved, then use that method to charge the customer or to create a subscription.
For a card, the event means the card is saved and chargeable. For a Bacs Direct Debit mandate it means the customer finished the form and the mandate was submitted — Bacs confirms it a few working days later, so key off dd_mandate.active / dd_mandate.failed for the outcome. You can still start collecting immediately; see Direct Debit.
Creating a subscription through checkout
Use SUBSCRIPTION mode when the customer needs to provide payment details before you create their subscription. Creating the link does not create a subscription. The hosted page shows the recurring total including tax, the billing interval, and any trial or scheduled start before the customer selects Agree and subscribe.
The API key needs both payments.write and subscriptions.write. Supply a customer and subscription_data.items referencing active recurring prices on the same account. Quantities must be positive, and all prices must share the same currency and recurrence. You do not need a saved payment method in advance.
curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/checkout-sessions \
-H "Authorization: Api-Key $API_KEY" \
-H "Idempotency-Key: YOUR_UNIQUE_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "SUBSCRIPTION",
"customer": "accounts/acct_YOUR_ACCOUNT/customers/cus_CUSTOMER_ID",
"subscription_data": {
"items": [{
"display_name": "Pro Plan",
"price": "accounts/acct_YOUR_ACCOUNT/prices/pr_PRICE_ID",
"quantity": 1
}],
"metadata": { "membership_id": "membership_123" }
},
"metadata": { "signup_id": "signup_123" },
"success_url": "https://example.com/subscription-created",
"cancel_url": "https://example.com/plans"
}'Redirect the customer to the returned url. Omit amount and payment_intent_data; the currency comes from the prices. If you supply currency, it must match. Checkout offers eligible reusable card or Bacs Direct Debit methods that support automatic collection in that currency; use payment_method_types to restrict the choice further.
The subscription uses CHARGE_AUTOMATICALLY. Set subscription terms such as default_tax_rates, grace_period_days, or disable_payment_receipt inside subscription_data. Its metadata is copied to the subscription; top-level metadata belongs to the checkout session. The returned subscription_preview shows the recurring cost, including tax, and is not an issued invoice.
For a trial or scheduled start, set either subscription_data.trial_end or subscription_data.start_date, not both. If that date is earlier than expire_time, the link expires at that date instead, so check the returned expire_time. The link must stay open for at least 30 minutes. A date sooner than that fails with an error asking you to choose a later expiry or move the subscription dates. Signup terms are fixed for that link: expire it and create another if they change. See Subscriptions for billing and tax behavior.
Sandbox testing: With a simulated customer clock, the expiry cap uses the time remaining until the trial end or start date on that clock. The checkout link itself expires in real time.
Knowing when signup is complete
Listen for checkout_session.subscription_created. Its checkout_session payload contains state: COMPLETE, the newly created subscription resource name, and your session metadata. Read that subscription for its state and default payment method; do not create another subscription in response to the event.
paid remains false. The billing engine collects invoices separately according to the subscription's schedule, so the first collection can fail even after checkout completes. Use invoice and subscription events to track payment and access. A Bacs signup can complete while its setup intent is still PROCESSING; mandate activation and collection settlement happen later.
Replacing a subscription's payment method
Use the same mode with subscription instead of subscription_data to let a customer save new payment details for an existing subscription:
curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/checkout-sessions \
-H "Authorization: Api-Key $API_KEY" \
-H "Idempotency-Key: YOUR_UNIQUE_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "SUBSCRIPTION",
"subscription": "accounts/acct_YOUR_ACCOUNT/subscriptions/sub_SUBSCRIPTION_ID",
"metadata": { "request_id": "update_123" },
"success_url": "https://example.com/payment-method-updated"
}'This requires both write permissions above. The customer and currency are derived from the subscription; if you supply them, they must match. The subscription must use CHARGE_AUTOMATICALLY and be in SCHEDULED, TRIALING, ACTIVE, INCOMPLETE, PAST_DUE, or UNPAID.
On completion, checkout_session.subscription_payment_method_updated carries the session and its target subscription. The saved method becomes that subscription's default_payment_method. This does not retry an outstanding invoice or reactivate an unpaid subscription. Normal scheduled billing continues; handle payment recovery separately.
Key fields
| Field | Description |
|---|---|
mode | PAYMENT (default), SETUP, or SUBSCRIPTION; see Choosing a mode |
amount | Amount in smallest currency unit (e.g. 10000 = 100.00 GBP). Required in PAYMENT mode; zero or omitted in SETUP and SUBSCRIPTION modes |
currency | Three-letter ISO currency code. Required in PAYMENT mode; optional rail filter in SETUP mode; derived from the subscription prices in SUBSCRIPTION mode |
customer | Required for SETUP and subscription signup; derived from the subscription for replacement. In PAYMENT mode it offers saved cards and enables the save-card option |
payment_method_types | Restricts which payment rails the customer may pick. Empty means every eligible rail on your account is offered |
client_reference_id | Your own reference (e.g. order ID) for cross-referencing |
success_url | URL to redirect the customer to after they complete the page. If not set, a Synapto hosted success page is shown |
cancel_url | When set, a "Back" link is shown on the checkout page that redirects here |
url | The hosted page URL. Only present when state is OPEN |
payment_intent | The payment intent created automatically for this session. PAYMENT mode only |
setup_intent | The setup intent created automatically in SETUP and SUBSCRIPTION modes |
subscription_data | Signup terms supplied at creation and returned on the session. SUBSCRIPTION mode only |
subscription | Existing replacement target, or the subscription created when signup completes |
paid | true once the checkout's payment has succeeded. Always false in SETUP and SUBSCRIPTION modes |
payment_intent_data | Configures the payment intent the session creates: description (max 1000 chars, shown on the receipt) and disable_payment_receipt (see Receipts). PAYMENT mode only |
metadata | Key-value pairs for your own use (max 50 keys, key max 40 chars, value max 500 chars) |
Checkout session states
| State | Description |
|---|---|
OPEN | Customer can visit the URL and complete the page |
COMPLETE | Payment succeeded (PAYMENT), a method was saved (SETUP), or the subscription was created or its method replaced (SUBSCRIPTION) |
EXPIRED | Session expired before the customer completed it (after 24 hours by default) |
Expiring a session
If an order is canceled or changes before the customer completes the page, expire the session to invalidate the link:
curl -X POST https://api.synaptopay.com/v1/accounts/acct_YOUR_ACCOUNT/checkout-sessions/cs_SESSION_ID:expire \
-H "Authorization: Api-Key $API_KEY"This cancels the session's managed intent — the payment intent, or the setup intent in SETUP and SUBSCRIPTION modes — and sets the session state to EXPIRED. If the customer completed the page in the meantime, the session is completed instead of expired. Subscription setup accepted before the deadline can still complete even if reconciliation happens after expiry. Acceptance means the provider successfully established the reusable payment method, not when the customer clicked the button. If setup finishes at or after expiry, the method may be saved, but checkout cannot create or update the subscription.
Webhooks
Subscribe to the event for the flow you created. Each carries the session, including its metadata:
| Flow | Completion event |
|---|---|
| One-time payment | checkout_session.paid |
| Save a payment method | checkout_session.setup_succeeded |
| Subscription signup | checkout_session.subscription_created |
| Subscription payment-method replacement | checkout_session.subscription_payment_method_updated |
Reconcile using these events rather than relying on the customer returning to success_url. Subscription events confirm creation or assignment, not payment. See Webhooks for setup instructions.
A subscription link can become invalid if its price is archived, its target subscription is no longer eligible, or the account is restricted. If this happens after payment details are saved but before checkout completes, the session becomes EXPIRED without a subscription completion event; signup creates no subscription, and replacement leaves the subscription's payment method unchanged. Expire open links before changing their terms or archiving their prices. Treat the completion event or a session in COMPLETE state as confirmation of success, not payment details being saved; check the session state when investigating an unfinished signup and issue a new link after resolving the problem.
Updated about 1 month ago