Create Checkout Session
Opens a checkout session. No credentials required. Pass exactly one of items, checkout_configuration, or link. The response includes client_secret once; later calls authenticate with it.
Headers
A unique key that makes this request safe to retry. See Idempotent requests.
255"d9105228-4a08-46b1-8b91-42fed586d383"
Pins the request to a dated API version.
"2026-08-21-1"
Body
The affiliate this checkout is attributed to. Write-once — set it here or never.
"aff_from_caller"
String-to-string acquisition context. Recognized keys: utm_source, utm_medium, utm_campaign, utm_term, utm_content, tracking_link_id, funnel_id, source, country; anything else is dropped.
A seller's checkout configuration (ch_…) to open this checkout from. Its plan, mode, affiliate code, metadata, redirect URL, 3DS level and payment method configuration seed the session; anything you also send explicitly wins.
"ch_xxxxxxxxxxxxxx"
What the buyer is purchasing. Exactly one entry today — more are refused until multi-item checkout ships; the array shape is the forward contract. Alongside a checkout_configuration or link it may only name that mount's own plan, where it sets quantity.
Any checkout link the seller has shared, resolved for you: a plan ID, a checkout configuration ID, a vanity short link (send page_route with it), a membership transfer code, or a checkout link the seller handed out earlier. A link that is not a checkout link is refused with a coded message rather than a bare not-found.
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Free-form string-to-string map, at most 40 keys. Whop never interprets it.
Defaults to the checkout configuration's mode, then payment. setup sessions are not yet available and are refused.
payment, setup, null "payment"
Where this checkout is being opened from — the scheme and host of your page, with no path (https://shop.example.com). Ignored when the request carries a browser Origin header, which is used instead. Recorded against the session as acquisition context.
The product route a vanity link belongs to — the pageRoute in the seller's shared URL.
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
The password for a password-protected plan. Right, and the gate is cleared for the session's whole life; wrong or omitted, and the session still opens — it publishes a custom_password requirement, the answer arrives through update, and confirm refuses until it is right.
"rotated-away"
A promo code to apply to the quote.
Where the buyer lands after an off-site payment step. Absolute https URL without credentials.
An existing membership (mem_…) this checkout pays against instead of creating a new one — the buyer pays the plan's price again onto something they already own. Ownership is checked at confirm, against the buyer who confirms: a membership they do not own is refused as not found. Cannot accompany a membership transfer link.
"mem_xxxxxxxxxxxxxx"
First-party tracking-link candidates keyed by account ID. Ignored outside Whop's hosted checkout; an explicit attribution.tracking_link_id wins.
Response
a free plan, which needs no payment method
The affiliate this checkout is attributed to, or null. Set at create only.
"aff_from_link"
Every value display_currency accepts: the seller's own currency, plus the buyer's local one when the seller has adaptive pricing on and Whop can price in it. Anything else is refused. A checkout that takes no money — a free plan, a transfer, a promo code that makes every charge free — offers the seller's currency alone: there is nothing to pay, so there is no currency to choose.
The seller's checkout appearance, resolved configuration first, then plan, then account. null when nobody set any.
The buyer's email, once one has been resolved for this checkout — null until then. Read-only, and read it together with buyer_identity: an address alone does not mean the person holds it.
"marcus@shinetime.example"
The buyer's user account (user_…), once one has been resolved for this checkout — null until then. Read-only, and read it together with buyer_identity: attributed names the account the purchase is for and proves nothing about who is at the keyboard.
"user_xxxxxxxxxxxxxx"
How well this checkout knows its buyer, or null before one is resolved. attributed means an account was matched from what the buyer typed — it says who the purchase is for and nothing about who is at the keyboard. authenticated means the person proved they hold that account during this checkout. Only authenticated may be handed anything that acts as the buyer, and the value only strengthens.
attributed, authenticated, null "attributed"
When the buyer's identity was last established or strengthened, as an ISO 8601 timestamp. null before a buyer is resolved.
"2026-01-01T12:00:00.000Z"
How the buyer's identity was established — the mechanism behind buyer_identity, so a checkout stays explicable long after it completed. null before a buyer is resolved. New mechanisms are added over time.
confirmation_token_email, buyer_email, saved_payment_method, session_intent, post_purchase_claim, null "confirmation_token_email"
The seller's checkout configuration this session was mounted from (ch_…), or null when it was opened directly from a plan. Its presets seeded this session at create.
"ch_xxxxxxxxxxxxxx"
Whether this seller offers tax ID entry on checkout (their VAT ID setting). When false, hide the input — a stored tax_id still applies either way.
false
When the session was created, as an ISO 8601 timestamp.
"2026-01-01T12:00:00.000Z"
The verb for the button that confirms this checkout, so every surface names the act the same way: pay, subscribe, start_trial, join_waitlist, or continue when nothing is charged today (a free checkout, setup mode saving a payment method, or a transfer that charges nothing). Render your own wording for each value — this is a key, never display text — and fall back to a generic label on a value you do not recognize.
accept_transfer, pay, continue, start_trial, subscribe, join_waitlist "subscribe"
The currency this checkout is priced AND charged in, lowercase. Seeded at create from where the buyer is; update it to one of available_currencies to price and charge in that currency instead.
"usd"
The waitlist entry the confirm created, or null. Only a waitlist plan produces one: joining charges nothing — the payment method is saved and the seller charges it if they accept the entry — so a completed waitlist session carries an entry and a null payment. Read it with its status: succeeded means the join stands; failed (the card save died — the buyer is not on the waitlist) is transient, reopening the session on the next read with the failure on last_confirm_error.
When the session expires, as an ISO 8601 timestamp. An expired session cannot be updated or confirmed — start a new one.
"2026-01-01T12:00:00.000Z"
Checkout session ID, prefixed chs_.
"chs_xxxxxxxxxxxxxx"
When this checkout's invoice is due, as an ISO 8601 timestamp — null for everything that is not an invoice checkout, and for an invoice without a due date. Present when the plan collects a seller-issued invoice; a surface should state the date, and a date in the past reads as overdue.
Why the most recent confirm attempt did not complete, or null. Only terminal refusals land here — anything the buyer can resolve is a next_action instead. The session stays open; fix what the code names and confirm again.
Free-form string-to-string map set at create. Whop never interprets it.
payment charges the items at confirm; setup saves a payment method for later use.
payment, setup "payment"
The one thing to do now, or null when there is nothing. Do it, re-read the session, and repeat until it is null. A completed session with no next_action has nothing left to do — whether the checkout WENT THROUGH is the result's own status (payment.status, or entry.status for a waitlist join): a charge that decisively dies REOPENS the session on the next read, with the failure on last_confirm_error — so a failed result is a transient reading, never a resting state, and nothing about it may be presented as a success. type picks the shape, and each type carries only its own fields: switch on type and the payload it needs is there; a type you do not recognize is safe to skip when it is not blocking. blocking says whether the flow may move past it — read it rather than inferring it from the type. Before the charge, blocking means the buyer cannot pay until it is done. complete is the attempt's own unfinished ceremony — a 3D Secure challenge, a bank redirect — and one executor serves it whatever its kind: hand its client_secret to the payments element's handleNextAction. After it, wait_for_payment is blocking because the money is still moving and everything past that point would tell the buyer their order landed — hold, wait poll_after_seconds, and read again. The post-completion actions (upgrade_authentication, redirect, await_claim) are advisory: the purchase stands either way. After completion the action can also depend on WHO is reading: a signed-in buyer may be handed the redirect where an anonymous read of the same session gets await_claim — told to check their email to claim the purchase, or that the checkout finished on another device — and a read from outside whop.com's own pages (an embedded element) may be handed a redirect whose destination is whop.com's checkout-finish page instead of the seller's: execute it like any other redirect, and treat the URL as the credential it carries. This is an instruction, never the gate: confirm re-checks everything for itself, so a client that skips a blocking action is refused all the same. upgrade_authentication carries its own recipe (email, sign_in_intent); when its session_intent_id is non-null, the reader's own mid-checkout sign-in already proved the first factor and the recipe collapses to its last step — call that session intent's upgrade endpoint directly (the credential the sign-in installed authorizes it) and collect only what the login still owes.
- Authenticate
- Verify phone
- Complete
- Wait for payment
- Upgrade authentication
- Redirect
- Await claim
Always checkout_session.
"checkout_session"
The payment the confirm created, or null while none exists. Read it with its status: a completed session's payment can still be requires_action (a step remains) or processing (accepted, settling). failed is a transient reading — a charge that decisively dies reopens the session on the next read, unbinding the dead payment and recording the failure on last_confirm_error. Anything the charge still needs comes through next_action, which is also where the payment's own credential lives.
The configuration governing this session, resolved through every layer — the checkout configuration it was mounted from, the plan's, and the account's. Apply it over the payment method types catalogue for the offerable set. null means nothing is configured at any layer: platform defaults apply.
Whether confirm needs a confirmation_token. false when no charge will ever take money — a free plan, a transfer of a non-renewing plan, or an applied promo code that makes every charge free, now and forever: collect no payment method there and confirm without a token. Live like every session fact: applying or removing a code can flip it, so mount your payment surface off this field, never off a zero total — a checkout that charges nothing today but something later (a trial, a first-charge-only code) stays true.
true
The buyer's phone number, or null. Collected when the session publishes a phone_number requirement (the seller collects numbers), set through update, and recorded against the order. Buyer-typed and unverified — sellers who VERIFY numbers get the verify_phone next action instead, which writes to the buyer's account.
"+xxxxxxxxxxx"
What the applied code takes off and which charges it covers, or null when no code applies. promo_code is the writable field; this is its resolved semantics — the duration says which future charges the breakdown's upcoming amounts have the discount in.
The promo code applied to the quote, or null. Set it via update; the discount shows up in the quote.
"code2"
The current price of the items, computed by the server on every read. Confirm charges this number while it is fresh.
Where the SELLER sends the buyer after a completed purchase, when the checkout configuration this session was mounted from sets one. Always an absolute HTTP or HTTPS URL — anything else serializes as null. Distinct from return_url, which is the payment provider's return leg.
"https://shinetime.example/thanks"
Where the buyer lands after an off-site payment step. Must be an absolute https URL without credentials (http is allowed for localhost).
The seller this checkout purchases from.
Where physical goods ship, or null. Only the keys the buyer supplied are present.
Whether to offer promo code entry on this checkout: false on a membership transfer, a checkout with nothing due, or a one-off purchase — no code can apply — and when the seller disabled codes for it, or none exists to enter. A code already on the session still discounts either way.
true
open until a confirm succeeds (completed) or the session ages out (expired). Only an open session can be updated or confirmed. A completed session whose charge later decisively dies returns to open with the failure on last_confirm_error — the same session takes the retry.
open, completed, expired "completed"
Whether this checkout collects tax, and how its price reads when it does. null means no tax is collected here — skip calculate_tax entirely and ask for no more address than the payment method itself needs. exclusive means tax is ADDED to the quote, inclusive that the quote already contains it. Present before any address is known, so a surface can decide what to collect up front; the value is what this checkout expects to price with, and calculate_tax answers with the authoritative one once a location is known (tax behaviour varies by country).
inclusive, exclusive, null "inclusive"
The buyer's tax registration for a business purchase, or null. Set it via update (null clears it); a stored registration re-prices the quote's tax row — a valid EU B2B id reverse-charges EU VAT — and confirm charges tax under the same registration, so the shown total and the taken one agree.
The 3D Secure behavior the checkout configuration asks for, or null to use the plan's or the account's default.
mandate_challenge, frictionless, null "frictionless"
The membership this checkout tops up (mem_…), or null for an ordinary purchase. Set at create only. Confirming charges the plan's price against that existing membership instead of creating a new one — no stock is taken, and for an expiring plan the paid time stacks onto what is left. The buyer must own it: confirm resolves it against the resolved buyer's own memberships and refuses anything else as not found.
"mem_xxxxxxxxxxxxxx"
The membership transfer this checkout performs, or null for an ordinary purchase. Present when the session was mounted from a transfer link: confirming moves the linked membership to this buyer instead of creating a fresh purchase, the quote prices zero due today, and for a renewing plan the collected payment method takes over the origin's future renewals.
The session's one credential — returned by create, and echoed on a session read authenticated with it (or with the checkout's own resume cookie, which holds the same value). Every other call authenticates with it; treat it like a password for this checkout.
"chs_xxxxxxxxxxxxxx_secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

