Embedding Checkout


Embedded Checkout

Advance Embedded Checkout lets you collect payments directly within your application. Instead of redirecting customers to Advance-hosted pages, you embed the checkout experience in an iframe on your site — giving your users a seamless, branded payment flow.

How It Works

┌─────────────────────────────────────────────────────────────┐
│  Your Application                                           │
│                                                             │
│   1. Create a Plan or Payment Request via the Advance API   │
│   2. Receive a payment_url or payment_request_url           │
│   3. Embed the URL in an iframe with ?embed=true            │
│   4. Listen for postMessage events (success, error, settled)│
│                                                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  Advance Embedded Checkout (iframe)                   │  │
│  │                                                       │  │
│  │  ┌───────────────────────────────────────────────┐    │  │
│  │  │  Payment Form                                 │    │  │
│  │  │  - Credit Card / ACH                          │    │  │
│  │  │  - Amount & fee summary                       │    │  │
│  │  │  - Pay button                                 │    │  │
│  │  └───────────────────────────────────────────────┘    │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

When ?embed=true is appended to the checkout URL:

  • The Advance tenant logo is hidden — your customers see only the payment form
  • The checkout communicates payment status back to your page via window.postMessage
  • The checkout is optimized for iframe display

Quick Start

1. Create a Plan or Payment Request

Use the Advance API to create a resource and obtain a checkout URL.

Option A: Plan (Invoice-based)

curl -X POST "https://api.advancehq.com/v1/plans" \
  -H "Authorization: Bearer advance_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "insured_id": "ins_abc123",
    "effective_date": "2026-03-01",
    "allowed_payment_methods": ["checkout_credit_card", "checkout_ach"],
    "policies": [...],
    "line_items": [...]
  }'

Response includes payment_url:

{
  "plan_id": "plan_abc123",
  "payment_url": "https://app.advancehq.com/checkout/pay_xyz789",
  "invoice_pdf_url": "https://...",
  "status": "open"
}

Option B: Payment Request (standalone)

curl -X POST "https://api.advancehq.com/v1/payments/payment-request" \
  -H "Authorization: Bearer advance_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1500.00,
    "details": "Policy renewal payment",
    "recipient_email": "[email protected]",
    "allowed_payment_methods": ["checkout_credit_card", "checkout_ach"]
  }'

Response includes payment_request_url:

{
  "payment_request_id": "preq_abc123",
  "payment_request_url": "https://app.advancehq.com/payment-request/preq_abc123",
  "amount": 1500.00,
  "status": "pending"
}

2. Embed the Checkout

Append ?embed=true to the URL and render it in an iframe:

<iframe
  id="advance-checkout"
  src="https://app.advancehq.com/checkout/pay_xyz789?embed=true"
  style="width: 100%; height: 700px; border: none;"
  allow="payment"
></iframe>

3. Listen for Events

window.addEventListener('message', function (event) {
  // Verify the message origin
  if (event.origin !== 'https://app.advancehq.com') return;

  const { type, payload } = event.data;

  switch (type) {
    case 'advance.checkout.ready':
      console.log('Checkout loaded');
      break;

    case 'advance.checkout.success':
      console.log('Payment successful!', payload);
      // payload: { payment_id, amount, payment_method }
      break;

    case 'advance.checkout.error':
      console.log('Payment failed', payload);
      // payload: { error_code, error_message }
      break;

    case 'advance.checkout.settled':
      console.log('Checkout finished', payload);
      // Always fires after success or error
      // payload: { status: "succeeded" | "failed", payment_id?, error_code? }
      // Use this for cleanup: hide loading spinners, remove iframe, etc.
      break;
  }
});

That's it. Three steps to collect payments inside your app.


JavaScript SDK (Optional)

For a more streamlined integration, use the Advance Checkout SDK. It manages the iframe lifecycle and provides a callback-based API.

Include the Script

<script src="https://js.advancehq.com/checkout.js"></script>

Initialize Checkout

<div id="checkout-container"></div>

<script>
  const checkout = AdvanceCheckout.create({
    url: 'https://app.advancehq.com/checkout/pay_xyz789',
    container: '#checkout-container',
    height: '700px',
    onReady: function () {
      console.log('Checkout is ready');
    },
    onSuccess: function (payload) {
      // { payment_id, amount, payment_method }
      console.log('Payment succeeded:', payload);
    },
    onError: function (payload) {
      // { error_code, error_message }
      console.error('Payment failed:', payload);
    },
    onSettled: function (payload) {
      // { status: "succeeded" | "failed", payment_id?, error_code? }
      // Always called after onSuccess or onError
      // Use for cleanup regardless of outcome
      console.log('Checkout complete:', payload.status);
      checkout.destroy();
      window.location.href = '/order-confirmation';
    },
  });
</script>

SDK Methods

MethodDescription
AdvanceCheckout.create(options)Creates and mounts the checkout iframe
checkout.destroy()Removes the iframe and cleans up event listeners

SDK Options

OptionTypeRequiredDescription
urlstringYesThe payment_url or payment_request_url from the API
containerstringYesCSS selector for the container element
heightstringNoIframe height. Default: 700px
onReadyfunctionNoCalled when the checkout has loaded
onSuccessfunctionNoCalled on successful payment
onErrorfunctionNoCalled on payment failure
onSettledfunctionNoCalled after either success or error. Always fires.

Note: The SDK automatically appends ?embed=true to the URL. Do not add it manually when using the SDK.


Callback Lifecycle

Callbacks fire in a predictable order:

onReady
   │
   ▼
Customer completes payment
   │
   ├── Success ──▶ onSuccess(payload) ──▶ onSettled({ status: "succeeded", ... })
   │
   └── Failure ──▶ onError(payload)   ──▶ onSettled({ status: "failed", ... })
  • onSuccess and onError are mutually exclusive — exactly one fires per payment attempt
  • onSettled always fires after either onSuccess or onError
  • Use onSuccess / onError for outcome-specific logic (show confirmation vs. show retry)
  • Use onSettled for cleanup that should happen regardless: remove the iframe, hide spinners, log analytics

Events Reference

All events are dispatched via window.postMessage from the checkout iframe to the parent window.

EventDescriptionPayload
advance.checkout.readyCheckout has loaded and is ready for interaction{}
advance.checkout.successPayment was successfully processed{ payment_id, amount, payment_method }
advance.checkout.errorPayment attempt failed{ error_code, error_message }
advance.checkout.settledCheckout finished (fires after success or error){ status, payment_id?, error_code? }

Payload Details

advance.checkout.success

{
  "type": "advance.checkout.success",
  "payload": {
    "payment_id": "pay_xyz789",
    "amount": 1500.00,
    "payment_method": "credit_card"
  }
}

advance.checkout.error

{
  "type": "advance.checkout.error",
  "payload": {
    "error_code": "payment_declined",
    "error_message": "The card was declined. Please try a different payment method."
  }
}

advance.checkout.settled

After a successful payment:

{
  "type": "advance.checkout.settled",
  "payload": {
    "status": "succeeded",
    "payment_id": "pay_xyz789",
    "amount": 1500.00,
    "payment_method": "credit_card"
  }
}

After a failed payment:

{
  "type": "advance.checkout.settled",
  "payload": {
    "status": "failed",
    "error_code": "payment_declined",
    "error_message": "The card was declined. Please try a different payment method."
  }
}

Configuration

Query Parameters

Append these to the checkout URL when embedding manually (the SDK handles this automatically):

ParameterTypeDefaultDescription
embedbooleanfalseEnables embed mode: hides tenant branding, enables postMessage events

Payment Methods

Control which payment methods are available by setting allowed_payment_methods when creating the Plan or Payment Request via the API:

MethodDescription
checkout_credit_cardCredit/debit card via payment gateway
checkout_achACH bank transfer

If not specified, all available methods are shown.


Security

Authentication via API Keys

Embedded Checkout relies on your existing Advance API key for security:

  1. You create the Plan or Payment Request using your API key (server-side)
  2. You receive the checkout URL containing a unique, unguessable resource identifier
  3. You embed the URL in your frontend

The checkout URL is only accessible to someone who possesses the identifier — and only authenticated API key holders can create these resources. This means:

  • No API keys are exposed in your frontend code
  • No additional authentication is needed for the iframe
  • Each checkout URL is unique to a specific payment and cannot be reused after completion

Best Practices

PracticeDetails
Create URLs server-sideAlways call the Advance API from your backend. Never expose your API key in client-side code.
Validate message originWhen listening for postMessage events, always verify event.origin === 'https://app.advancehq.com'
Use HTTPSYour site must be served over HTTPS to embed the checkout
Verify payment server-sideAfter receiving a success event, confirm the payment status via GET /v1/payments/{payment_id} from your backend before fulfilling orders

Full Integration Example

End-to-end example: create a payment request from your backend, embed checkout on your frontend, and handle the result.

Backend (Node.js)

// POST /api/create-checkout — your backend endpoint
app.post('/api/create-checkout', async (req, res) => {
  const response = await fetch(
    'https://api.advancehq.com/v1/payments/payment-request',
    {
      method: 'POST',
      headers: {
        Authorization: 'Bearer advance_live_your_api_key',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        amount: req.body.amount,
        details: req.body.description,
        recipient_email: req.body.email,
        allowed_payment_methods: ['checkout_credit_card', 'checkout_ach'],
      }),
    }
  );

  const data = await response.json();

  res.json({
    checkout_url: data.payment_request_url,
    payment_request_id: data.payment_request_id,
  });
});

Backend (Python)

# your_app/routes.py
import httpx
from fastapi import APIRouter

router = APIRouter()

ADVANCE_API_KEY = "advance_live_your_api_key"  # Store securely in env vars

@router.post("/api/create-checkout")
async def create_checkout(amount: float, description: str, email: str):
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "https://api.advancehq.com/v1/payments/payment-request",
            headers={
                "Authorization": f"Bearer {ADVANCE_API_KEY}",
                "Content-Type": "application/json",
            },
            json={
                "amount": amount,
                "details": description,
                "recipient_email": email,
                "allowed_payment_methods": ["checkout_credit_card", "checkout_ach"],
            },
        )
        data = response.json()

    return {
        "checkout_url": data["payment_request_url"],
        "payment_request_id": data["payment_request_id"],
    }

Frontend

<!DOCTYPE html>
<html>
  <head>
    <title>Checkout</title>
    <script src="https://js.advancehq.com/checkout.js"></script>
  </head>
  <body>
    <h1>Complete Your Payment</h1>
    <div id="checkout-container"></div>
    <div id="status" style="display: none;"></div>

    <script>
      async function startCheckout() {
        // 1. Create checkout via your backend
        const res = await fetch('/api/create-checkout', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            amount: 1500.0,
            description: 'Policy renewal',
            email: '[email protected]',
          }),
        });
        const { checkout_url, payment_request_id } = await res.json();

        // 2. Mount the embedded checkout
        const checkout = AdvanceCheckout.create({
          url: checkout_url,
          container: '#checkout-container',
          onSuccess: function (payload) {
            console.log('Payment succeeded:', payload.payment_id);
          },
          onError: function (payload) {
            console.error('Payment failed:', payload.error_message);
          },
          onSettled: async function (payload) {
            // Always runs — clean up and move on
            checkout.destroy();

            if (payload.status === 'succeeded') {
              // Verify server-side before fulfilling
              await fetch('/api/verify-payment', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                  payment_id: payload.payment_id,
                  payment_request_id: payment_request_id,
                }),
              });
              window.location.href = '/confirmation';
            } else {
              document.getElementById('status').style.display = 'block';
              document.getElementById('status').innerText =
                'Payment failed. Please try again.';
            }
          },
        });
      }

      startCheckout();
    </script>
  </body>
</html>

Environments

EnvironmentCheckout Base URLAPI Base URL
Sandboxhttps://app-sandbox.advancehq.comhttps://api-sandbox.advancehq.com
Productionhttps://app.advancehq.comhttps://api.advancehq.com

Use the Sandbox environment for development and testing. Sandbox checkout simulates payment processing without moving real funds.


FAQ

Can I style the checkout to match my brand? The embedded checkout uses a neutral, minimal design when embed=true is set. The Advance tenant branding is hidden so it blends into your site. Custom CSS theming is on our roadmap.

What happens if the customer closes the browser mid-payment? No payment is processed until the customer completes the form and confirms. Incomplete sessions are automatically cleaned up. You can check the payment status at any time via the API.

Can I embed checkout in a mobile app? Yes. Use a WebView (Android) or WKWebView (iOS) pointed at the checkout URL with ?embed=true. Listen for URL changes or use the JavaScript bridge to capture postMessage events.

Is the embedded checkout PCI compliant? Yes. All sensitive payment data (card numbers, bank accounts) is collected directly by our PCI-certified payment processor within the iframe. Card data never touches your servers or ours.

Can I pre-fill customer information? Customer details (email, name) can be set when creating the Plan or Payment Request via the API. The checkout will display these pre-filled values.

What if a payment link expires? For Payment Requests, you can set payment_expiration when creating the request. Expired links display a clear message to the customer. Create a new Payment Request to generate a fresh link.


Example Repository

For a complete working example, see the Advance Checkout Embed Example repository.