Skip to main content
The platform hosts the page where a patient pays. Your storefront never renders a card field, never holds a payment credential, and never relays a payment token — it hands the patient to the hosted checkout with a link, and your servers learn the outcome afterwards. This page describes that door: the entry URL, what the link carries, and the reads that tell you what happened.
Fastest start — the site kit. You do not have to build this door from scratch. Purple ships a fork-and-own storefront starter: a complete Next.js site that already stands up the storefront leading to this door, including the hand-off link that brings a patient here. Fork it, brand it, and deploy it as your own.Fork a tagged release — never the tip of the main branch — so your site is pinned to a known, supported version. Take the latest tag from the starter’s releases page. While the repository is private, access is provided at onboarding: your onboarding contact points you at the repository and the release to fork.The in-repo site-kit-quickstart guide is your operator’s manual — it takes a fork from first run to a live, branded site.What the starter gives you is the site itself: the storefront and the brand slots, plus a working reference for the calls this page describes. What the platform issues you is what makes that site yours — your credentials at onboarding, your brand, and your assigned offerings. The starter ships identical to everyone; your configuration is what makes it your brand’s door.

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_id and therapy instead, and the enrollment is started at the door: the hosted checkout’s own server calls POST /v1/prospects to 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 at lib/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 with POST /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 with POST /v1/webhooks/registrations/{registration_id}/test.
  • Reads you make. GET /v1/payments/{order_ref}/status returns 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}/status returns 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.
Two facts worth designing around: a failed payment still leaves a readable order — the attempt is never invisible — and a promo code the platform cannot honor is refused with the exact reason, so a patient is never quietly charged full price against a code they believed in.