Embedded checkout (iFrame)

The iFrame is an alternative presentation of the standard flow—the same eligibility survey, card entry, statuses, and webhooks as the hosted redirect, just rendered inline on your page instead of sending the customer to a Truemed-hosted page. Use it when you want customers to stay on your site through checkout.

It works for both entry points:

  • One-time paymentscreate_payment_session, for a single checkout.
  • Subscriptionscreate_payment_token, where the customer takes the survey and stores their cards once, and you charge on your own schedule afterwards.

Everything you build for the redirect flow still applies—creating the session or token, handling the webhook, and fulfilling on it. Only two things change:

Hosted redirect (default)Embedded iFrame
Create the session or tokencreate_payment_session / create_payment_tokenThe same call with use_iframe: true
Where the customer checks outA Truemed-hosted pageInline, in an iframe on your page
How you learn the resultsuccess_url / failure_url redirect + webhookpostMessage event + webhook

Fulfillment is unchanged: the payment_session_complete webhook with status: captured is still the source of truth. The postMessage success flag is only for updating your own UI.

Enable the iFrame

Add the optional use_iframe parameter to create_payment_session or create_payment_token. When use_iframe is true, the redirect_url in the response is an iframe-compatible URL you render inline instead of redirecting to.

If every checkout on your integration is embedded, ask your Truemed contact to enable it on your sales channel instead. The returned URL is then always iframe-compatible and you can leave the parameter out of your requests entirely.

Subscriptions: the whole signup happens in one frame

On create_payment_token the customer takes the health survey and then enters their cards. Both steps render inside the same frame, one after the other—you set src once and do not swap it between steps or manage the sequence. The message arrives when the customer has finished storing their cards, and your page never navigates along the way.

Example request

1{
2 "use_iframe": true,
3 "total_amount": 0,
4 "order_items": [],
5 "success_url": "https://example.com/success",
6 "failure_url": "https://example.com/failure",
7 "idempotency_key": "key",
8 "customer_email": "customer@example.com",
9 "customer_name": "Customer"
10}

Example response

1{
2 "id": "session_id",
3 "redirect_url": "https://truemed.com/..."
4}

Environments and local development

There is nothing to install and nothing to configure on your side. No SDK, no publishable key in the browser, and no origin to register with us before you can start — you set an iframe src and add one listener.

Two values differ between environments:

SandboxProduction
API base — server-side onlyhttps://dev-api.truemed.comhttps://api.truemed.com
Truemed origin — what you compare event.origin againsthttps://dev.truemed.comhttps://app.truemed.com

You never hardcode the frame’s URL; it arrives as redirect_url on the create call. The origin is the only Truemed value that belongs in your frontend config, so one setting per environment covers it.

Sandbox API keys come from the sandbox dashboard at dev.truemed.com/developers/api-keys. Keys are not shared across environments.

Running against localhost

A page served from http://localhost can embed the sandbox flow with no setup on either side. We send no X-Frame-Options header and no frame-ancestors policy on the framed pages, and those pages authenticate on identifiers already present in the URL rather than on cookies—so third-party-cookie rules and browser privacy settings have no bearing on the embed. A local page behaves the same as your production page.

We do not post height or resize messages. The completion message is the only one you receive from us, so give the frame generous height and let it scroll internally rather than trying to size it to its contents.

If a create call returns 404

A 404 with {"error": "Page not found"} means the API key is wrong, or the sales channel is not enabled for the endpoint you are calling. We return 404 rather than 401 so that an unauthenticated caller cannot map which endpoints exist, which makes a credentials problem read like a bad path. If the path is right, check the key and the environment before you check the URL.

Handling iFrame responses

Set the iframe element’s src to redirect_url and Truemed’s survey and checkout render inside it. The embedded page never navigates your page and cannot remove its own iframe—when the customer finishes, Truemed posts a single completion message to your window via the browser’s postMessage API. That message is your signal to remove the iframe and update your page.

The completion message

The completion message is the only message Truemed sends. Its data is a plain object:

Customer completed checkout
1{ "success": true, "redirectUrl": "https://example.com/success" }
Customer exited or was ineligible
1{ "success": false, "redirectUrl": "https://example.com/failure" }
FieldTypeDescription
successbooleantrue when the customer completed the flow, false when they exited or were ineligible. For your UI only—fulfill from the payment_session_complete webhook.
redirectUrlstringAn echo of the success_url or failure_url you provided on the create call, matching success. In the hosted flow Truemed redirects the browser to these URLs; inside an iframe it can’t, so it hands you the URL instead. Navigate to it for hosted-flow behavior, or ignore it and update your page in place.

Check event.origin before you act on a message. Your page receives messages from anything it frames, including the payment provider during a 3D Secure challenge, and any page on the internet can frame yours and post a message that looks exactly like ours. Comparing the origin against the Truemed origin is the only check that tells our message apart from an imitation of it. The origin for each environment is in the table above, and it differs between sandbox and production—read it from your environment config rather than hardcoding one, or the wrong build will discard every message we send.

Pair it with event.source !== iframe.contentWindow if your page can open checkout more than once, so a completion message is only ever handled by the frame that is currently open.

Apple Pay requires allow="payment" on the iframe and HTTPS on the parent page. Card payments and Link work without it.

3D Secure

If the customer’s bank asks them to authenticate, the 3D Secure challenge renders inside the embedded flow and the customer completes it there. There is no redirect, no popup, and nothing extra to build.

Give the frame enough height for it—the challenge is the bank’s own screen, and a short frame makes it awkward to use. Note that your message listener will also receive events from the authentication provider while the challenge is open, which is one reason to be deliberate about which messages you act on (see below).

Example handler

1// The one Truemed value that belongs in your frontend config. Read it per environment —
2// 'https://dev.truemed.com' in sandbox, 'https://app.truemed.com' in production — rather than
3// hardcoding a literal, or a sandbox build will silently discard every message production sends
4// and vice versa.
5const TRUEMED_ORIGIN = window.APP_CONFIG.truemedOrigin;
6
7function openTruemedIframe(redirectUrl) {
8 const iframe = document.createElement('iframe');
9 iframe.id = 'truemed-checkout';
10 iframe.src = redirectUrl;
11 iframe.allow = 'payment';
12 iframe.style.width = '100%';
13 iframe.style.height = '100%';
14
15 document.body.appendChild(iframe); // Or append to any desired element
16
17 function handleMessage(event) {
18 if (event.origin !== TRUEMED_ORIGIN || event.source !== iframe.contentWindow) {
19 // Anything else on the page — including the 3D Secure challenge — lands here. Ignore it.
20 return;
21 }
22
23 const { success, redirectUrl } = event.data ?? {};
24 if (success === undefined || redirectUrl === undefined) {
25 // Handle no data or invalid data shape
26 // We typically recommend ignoring these, defaulting to no-op unless you explicitly recognize the event.
27 return;
28 }
29
30 // Tear down before you act on the result, so that reopening checkout after a failure
31 // does not leave an earlier listener bound to a frame that is already gone.
32 window.removeEventListener('message', handleMessage);
33 document.body.removeChild(iframe);
34
35 if (success) {
36 // Handle success
37 // ...
38 // or redirect to previously provided success_url
39 window.location.href = redirectUrl; // Redirect to success URL
40 } else {
41 // Handle failure
42 // ...
43 // or redirect to previously provided failure_url
44 window.location.href = redirectUrl; // Redirect to failure URL
45 }
46 }
47
48 window.addEventListener('message', handleMessage);
49}