Iframe Integration
Step-by-step guide for integrating the Hosted Payment Page into your website through the iframe.
Flow summary: your backend creates a secure session (authenticated via OAuth 2.0) → receives a
sessionToken→ your frontend loads the SDK and mounts the iframe with that token → the customer pays → you receive the result via callback or redirect → your backend confirms the transaction.
Prerequisites
Before you start, you need the credentials provided by Inyo:
| Credential | Description |
|---|---|
OAUTH_CLIENT_ID | Identifier for your tenant (client). |
OAUTH_CLIENT_SECRET | Secret used to authenticate session creation. |
| Backend URL | Sessions API endpoint (referred to below as https://{HPP_API}). |
| Frontend URL | Origin of the hosted page that runs inside the iframe (referred to below as https://{HPP_HOST}). |
⚠️ The
OAUTH_CLIENT_SECRETmust never appear in the frontend. All session creation happens on your server.
Step 1 — Create a payment session (on your backend)
Before showing the iframe, your server creates a session. This call is protected by OAuth 2.0 Client Credentials (HTTP Basic Auth).
Endpoint: POST /api/sessions/create
Authentication: header Authorization: Basic base64(clientId:clientSecret)
Request body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | Number | Yes | Transaction amount (e.g. 50.00). Use 0 for card_validation. |
currency | String | Yes | Currency code (currently only USD). |
transactionType | String | Yes | payment, card_validation, or account_validation. |
parentOrigin | String | Yes | HTTPS origin of the page that hosts the iframe (e.g. https://your-site.com). |
capture | Boolean | No | true captures immediately; false only authorizes (default). |
tokenType | String | No | one_time (default) or recurring. |
customCss | String | No | URL of a hosted CSS file to customize the look. |
billingPreference | String | No | Billing fields visibility: editable (default), read_only, or hidden. |
billingDetails | Object | No | Pre-fills the billing data (name, email, address, etc.). |
threeDSData | Object | No | 3D Secure configuration: { "enable": true, "successUrl": "...", "failUrl": "..." }. |
metadata | Object | No | Free-form tracking data (e.g. orderId, customerId, uiOrder). |
Example (Node.js)
const clientId = process.env.OAUTH_CLIENT_ID;
const clientSecret = process.env.OAUTH_CLIENT_SECRET;
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
const response = await fetch('https://{HPP_API}/api/sessions/create', {
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 50.00,
currency: 'USD',
transactionType: 'payment',
parentOrigin: 'https://your-site.com',
billingPreference: 'editable',
threeDSData: { enable: true },
metadata: { orderId: 'ORD-789' }
})
});
const session = await response.json();
// Return ONLY the sessionToken to your frontend.
Response
{
"sessionToken": "eyJ...",
"sessionId": "sess_uuid",
"expiresAt": "2026-06-16T12:15:00Z",
"tenant": {
"name": "Your Company",
"logoUrl": "https://...",
"supportEmail": "support@...",
"primaryColor": "#3b82f6",
"paymentMethods": ["card", "ach"]
}
}
The
sessionTokenexpires in 15 minutes by default. Create the session only when the customer is ready to pay.
Step 2 — Load the SDK on your frontend
Include the SDK script on the page where checkout will be displayed and add a container (a <div>) where the iframe will be mounted.
<!-- Container where the iframe will be inserted -->
<div id="checkout-container"></div>
<!-- Checkout SDK -->
<script src="https://{HPP_HOST}/iframe.min.js"></script>
Step 3 — Initialize and mount the iframe
Use the sessionToken obtained in Step 1 to initialize the SDK and mount the iframe in the container.
<script>
InyoCheckout.init({
sessionToken: 'SESSION_TOKEN_FROM_BACKEND',
// Called when the payment is approved/authorized
onSuccess: (data) => {
console.log('Payment approved!', data.transaction);
// data.transaction = { status, paymentId, cardToken, billingInfo }
// IMPORTANT: confirm on your backend before fulfilling the order (Step 5).
},
// Called when the payment is declined or an error occurs
onError: (error) => {
console.error('Payment failed:', error);
}
});
InyoCheckout.mount('#checkout-container');
</script>
Options accepted by init
| Option | Description |
|---|---|
sessionToken | Required. Token returned in Step 1. |
onSuccess(data) | Success callback. Receives { transaction }. |
onError(error) | Error/decline callback. |
storeLaterUse | true to show the "save card for future use" option. |
threeDSData | 3D Secure configuration (mirrors what was sent on the session). |
The
sessionTokendoes not go in the iframe URL — the SDK sends it internally viapostMessage(INIT_SESSION) after the iframe loads. The iframe is also loaded in sandbox mode for security.
Step 4 — Receive the payment result
You can handle the result in two ways (use one or both).
Option A — JavaScript callbacks
The SDK fires onSuccess or onError automatically. The data.transaction object contains:
{
status: 'APPROVED' | 'AUTHORIZED' | 'DECLINED' | 'CHALLENGE' | 'PENDING',
paymentId: 'pay_12345',
cardToken: { token: '...', schemeId: 'VISA', lastFourDigits: '4242' },
billingInfo: { /* filled billing address */ }
}
Example:
onSuccess: (data) => {
const { status, paymentId } = data.transaction;
if (status === 'APPROVED' || status === 'AUTHORIZED') {
window.location.href = `/confirmation?pid=${paymentId}`;
}
}
Option B — Redirect (server-side)
If you provided successUrl / failUrl when creating the session (inside threeDSData or in the session configuration), the iframe redirects the whole page at the end, appending the parameters:
https://your-site.com/success?sessionId=sess_123&paymentId=pay_456
Possible statuses
| Status | Meaning |
|---|---|
AUTHORIZED | Authorized (not yet captured). |
APPROVED / CAPTURED | Payment completed. |
DECLINED | Card declined. |
CHALLENGE | Requires 3D Secure verification (handled automatically by the iframe). |
PENDING | Asynchronous payment (e.g. ACH) awaiting confirmation. |
Step 5 — Confirm the transaction on your backend (required)
⚠️ Never fulfill the order based solely on the frontend callback. Always confirm the status from your server.
Option 1 — Query the session status
GET /api/sessions/:sessionId Header: x-session-token: <sessionToken>
const res = await fetch(`https://{HPP_API}/api/sessions/${sessionId}`, {
headers: { 'x-session-token': sessionToken }
});
const session = await res.json();
if (session.status === 'completed') {
// Fulfill the order safely.
}
Option 2 — Verify against the gateway by paymentId
POST /api/verify-transaction Header: x-session-token: <sessionToken> Body: { "paymentId": "pay_123" }
Visual customization
The checkout appearance is fully customizable:
- Custom CSS: send a
customCss(URL of a hosted stylesheet) when creating the session to override the default styles. - Brand color: defined on the tenant record (
primaryColor), applied to buttons and highlights. - Billing fields: control visibility with
billingPreference(editable,read_only,hidden) and pre-fill withbillingDetails. - Section order: control the order of the billing and payment sections via
metadata.uiOrder(e.g.["billing", "payment"]).
Automatic iframe behavior
- Resizing: the iframe adjusts its own height as the content changes (via the
RESIZE_IFRAMEmessage). Do not set a fixed height. - 3D Secure: when required, the iframe handles the bank redirect and ACS verification automatically, validating the result on the backend before firing
onSuccess/onError. See Handling 3D secure for the underlying gateway behavior.
postMessage protocol (reference)
The SDK and the iframe communicate over window.postMessage. You normally do not handle these directly — the SDK translates them into the init callbacks — but they are documented here for debugging.
| Direction | Message type | Payload |
|---|---|---|
| Parent → iframe | INIT_SESSION | { sessionToken } |
| iframe → Parent | RESIZE_IFRAME | { height } |
| iframe → Parent | PAYMENT_SUCCESS | { transaction } → fires onSuccess |
| iframe → Parent | PAYMENT_ERROR | { error } → fires onError |
Integration checklist
- [ ] OAuth credentials (
clientId/clientSecret) kept on the backend only. - [ ] Session created via
POST /api/sessions/createon the server. - [ ]
parentOriginset correctly (HTTPS in production). - [ ] SDK (
iframe.min.js) loaded and iframe mounted with thesessionToken. - [ ]
onSuccess/onError(or redirects) handled on the frontend. - [ ] Transaction confirmed on the backend before fulfilling the order.
Full example (condensed)
Backend (creates the session):
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
const r = await fetch('https://{HPP_API}/api/sessions/create', {
method: 'POST',
headers: { 'Authorization': `Basic ${credentials}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
amount: 29.99,
currency: 'USD',
transactionType: 'payment',
parentOrigin: 'https://your-site.com',
metadata: { orderId: 'ORD-123' }
})
});
const { sessionToken } = await r.json();
// send sessionToken to the frontend
Frontend (mounts the iframe):
<div id="checkout"></div>
<script src="https://{HPP_HOST}/iframe.min.js"></script>
<script>
InyoCheckout.init({
sessionToken: sessionTokenFromBackend,
onSuccess: async (data) => {
await fetch('/api/confirm-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentId: data.transaction.paymentId })
});
},
onError: (err) => alert('Payment not completed.')
});
InyoCheckout.mount('#checkout');
</script>
