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
| Method | Description |
|---|---|
AdvanceCheckout.create(options) | Creates and mounts the checkout iframe |
checkout.destroy() | Removes the iframe and cleans up event listeners |
SDK Options
| Option | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The payment_url or payment_request_url from the API |
container | string | Yes | CSS selector for the container element |
height | string | No | Iframe height. Default: 700px |
onReady | function | No | Called when the checkout has loaded |
onSuccess | function | No | Called on successful payment |
onError | function | No | Called on payment failure |
onSettled | function | No | Called after either success or error. Always fires. |
Note: The SDK automatically appends
?embed=trueto 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", ... })
onSuccessandonErrorare mutually exclusive — exactly one fires per payment attemptonSettledalways fires after eitheronSuccessoronError- Use
onSuccess/onErrorfor outcome-specific logic (show confirmation vs. show retry) - Use
onSettledfor 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.
| Event | Description | Payload |
|---|---|---|
advance.checkout.ready | Checkout has loaded and is ready for interaction | {} |
advance.checkout.success | Payment was successfully processed | { payment_id, amount, payment_method } |
advance.checkout.error | Payment attempt failed | { error_code, error_message } |
advance.checkout.settled | Checkout 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):
| Parameter | Type | Default | Description |
|---|---|---|---|
embed | boolean | false | Enables 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:
| Method | Description |
|---|---|
checkout_credit_card | Credit/debit card via payment gateway |
checkout_ach | ACH 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:
- You create the Plan or Payment Request using your API key (server-side)
- You receive the checkout URL containing a unique, unguessable resource identifier
- 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
| Practice | Details |
|---|---|
| Create URLs server-side | Always call the Advance API from your backend. Never expose your API key in client-side code. |
| Validate message origin | When listening for postMessage events, always verify event.origin === 'https://app.advancehq.com' |
| Use HTTPS | Your site must be served over HTTPS to embed the checkout |
| Verify payment server-side | After 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
| Environment | Checkout Base URL | API Base URL |
|---|---|---|
| Sandbox | https://app-sandbox.advancehq.com | https://api-sandbox.advancehq.com |
| Production | https://app.advancehq.com | https://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.
Updated 18 days ago
