The entry link
The hosted checkout lives at/checkout on the platform’s patient portal — the same origin where
your patients sign in and manage their plan. The link carries the entry context as query
parameters:
Entry context selects what to present — never what anything costs. Malformed values are
dropped at the door, and every money figure on the page is resolved by the server from your
configured catalog and pricing. A link cannot name a price, a discount, or a fee; a request that
tries is rejected.
Two ways in
- After the questionnaire — the primary entry. The patient completes the health questions
first and arrives carrying
journey_id: identity, eligibility context, and enrollment are already attached, and payment is the only step left. - Direct buy — configured per deployment. Where your program is set up to take payment
before the health questions, a buy link carries
sku_idandtherapyinstead, and the enrollment is started at the door: the hosted checkout’s own server callsPOST /v1/prospectsto open it. That operation is not yet part of the published API reference — your storefront never calls it; the hosted surface makes the call on its own server. Which way your deployment works is configuration agreed when your program is set up, never inferred from the link — on a questions-first deployment, a link without an enrollment shows the patient a plain “health questions come first” stop instead of a payment form.
A reference implementation
The site kit ships a reference implementation of this entry contract. The hand-off composer atlib/purple/checkout-links.mjs builds the entry link for both ways in above — carrying
journey_id for the questionnaire-first entry, and taking sku_id and therapy for direct buy.
Read it as the worked example of the link this page specifies: the table above is the source of
truth for the wire, and the composer is a ready way to build it.
Sign-in at the door
Checkout requires sign-in. A patient who arrives anonymous starts with a single email field and signs in through the platform’s login — no password form is ever embedded in the page — while a patient who is already authenticated sees their identity collapsed to one bar with a sign-out. The patient’s account materializes at checkout; your storefront does not create it.Payment credentials never touch your code
The standing rule, stated plainly: no payment credential is ever handled client-side — not in your storefront, and not in the checkout page’s own frontend either. Card collection happens on the hosted page using Stripe’s browser components: the card number travels from the patient’s browser directly to the payment provider and never passes through the platform, or through you. There is no card field for you to build, no token for you to relay, and nothing payment-shaped for you to store. The browser side runs on the payment provider’s publishable key — not one of yours — and today that key is test-scoped only; a live-scoped key is refused outright. Your own publishable brand key is a separate credential with a different purpose, described in authentication. Behind the page, the hosted server creates the session withPOST /v1/payments/checkout-sessions under an idempotency key, so a retried submit continues
the same session instead of creating a second charge — and the required consent documents
configured for your deployment must be agreed to before that call is ever made. If no consent
documents are configured yet, payment stays locked: a patient is never charged without agreeing
to the configured set.
How your servers learn the outcome
After paying, the patient continues inside the portal — there is no browser redirect back to your storefront, and the outcome never rides the patient’s browser to reach you. Your servers learn what happened two ways:- Deliveries you subscribe to. Register an endpoint with
POST /v1/webhooks/registrations; the response returns your signing secret once, and every delivery carries a signature computed with it so your receiver can verify the sender. Deliveries are reference-only — an event type plus identifiers such as the order reference — so a delivery prompts a read but never leaks detail on its own. You can prove the pipe end to end at any time withPOST /v1/webhooks/registrations/{registration_id}/test. - Reads you make.
GET /v1/payments/{order_ref}/statusreturns the payment state of the order — awaiting payment, authorized, paid, failed, refunded — fed by confirmed events from the payment provider, never by guesswork.GET /v1/journeys/{journey_id}/statusreturns the patient’s public enrollment status, and a patient never shows as approved before their order is authorized — so the enrollment read is safe to drive your storefront’s own status copy. Read the payment status for the order’s own state: the enrollment read may return no status yet instead of a status value, and it would never carry payment detail in any case.