Hosted checkout and widget
With the hosted checkout the card never touches your server or your page. Your server opens a session, your page mounts the widget with the session id, and the shopper pays inside an iframe served by the gateway.
your server ── POST /api/payment-session (x-api-key) ──▶ gateway → sessionIdyour page ── widget.open({ sessionId }) ──▶ iframe {checkout}/c/{sessionId}the iframe ── card ──▶ gateway ──▶ processoryour page ◀── onPaymentReceived / onError / onStatus1. Open a session (server side)
Section titled “1. Open a session (server side)”Same body as Create a payment, without the card fields. Idempotency-Key is required:
sending the same key again returns the same session.
curl -X POST "$BASE_URL/api/payment-session" \ -H "x-api-key: $API_KEY" \ -H "Idempotency-Key: order_1001" \ -H "Content-Type: application/json" \ -d '{ "merchantIdentifier": "...", "midName": "...", "referenceCode": "ORDER-1001", "amount": "19.90", "currency": "USD", "email": "buyer@example.com", "postbackUrl": "https://merchant.example/hooks/pay" }'The answer (201) holds sessionId, checkoutUrl, status (CREATED), createdAt and expiresAt. A session lives 30 minutes and starts one sale.
Sending the same Idempotency-Key with a different body is refused with 409, so use a new key per order.
The final status also reaches your postbackUrl, as for any payment: treat the postback as the source of truth,
not the browser callback.
2. Mount the widget (your page)
Section titled “2. Mount the widget (your page)”<div id="checkout"></div><script src="https://YOUR-HOST/widget.js"></script><script> const widget = PayBridgeWidget.createWidget({ brand: 'paybridge', // or 'xcharger' }); widget.open({ sessionId, // from step 1; never put the API key in the page container: 'checkout', onPaymentReceived: (p) => {}, // APPROVED onError: (e) => {}, // DECLINED, ERROR, or an unreadable status onStatus: (s) => {}, // PENDING and other progress onRedirect: ({ url }) => {}, // 3-D Secure: open url in a popup window (optional) });</script>Your page’s origin must be allowed by the gateway (CORS_ALLOW_ORIGINS). A second open replaces the first iframe.
When the card needs 3-D Secure, the checkout stays on “pending” and onRedirect receives the challenge address. Open it
in a popup window (the demo does) with window.open. Browsers block popups that do not follow a click, so keep a link the
shopper can click as a fallback.
Without onRedirect nothing is shown and the shopper waits. Either way the result arrives through
onPaymentReceived or onError.
What the callbacks receive
Section titled “What the callbacks receive”| Callback | When | Argument |
|---|---|---|
onPaymentReceived |
The payment is APPROVED |
{ paymentId, amount, currency, status, occurredAt } |
onStatus |
The payment is PENDING or otherwise still moving |
{ status } |
onError |
DECLINED, ERROR or an unreadable status |
{ message, status?, paymentId? } |
onRedirect |
The processor needs 3-D Secure | { url } (an http(s) address) |
widget.close() removes the iframe. The browser callback is for your page’s feedback only. Confirm the order from
the postback or a lookup before you ship anything.
When something goes wrong
Section titled “When something goes wrong”| What you see | Likely cause |
|---|---|
| The iframe never appears | The sessionId is wrong or expired. Open a new session; each one starts a single payment. |
409 opening a session |
You reused an Idempotency-Key with a different body. |
| Browser blocks the request | Your page’s origin is not on the gateway’s allowed origins. |
onError after the shopper paid |
Do not charge again. Look the payment up by referenceCode first. |
Try it
Section titled “Try it”The widget demo opens a real sandbox session. Use the test cards, and remember that 3-D Secure cards stay inside the iframe.