---
description: >-
  Step-by-step guide to integrate the Hosted Payment Page iframe — create an OAuth-authenticated session on your backend, load the SDK and mount the iframe with the session token, receive the result via callback or redirect, and confirm the transaction server-side before fulfilling the order.
---

# 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_SECRET` must **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)

```javascript
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

```json
{
  "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 `sessionToken` expires 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.

```html
<!-- 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.

```html
<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 `sessionToken` does **not** go in the iframe URL — the SDK sends it internally via `postMessage` (`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:

```javascript
{
  status: 'APPROVED' | 'AUTHORIZED' | 'DECLINED' | 'CHALLENGE' | 'PENDING',
  paymentId: 'pay_12345',
  cardToken: { token: '...', schemeId: 'VISA', lastFourDigits: '4242' },
  billingInfo: { /* filled billing address */ }
}
```

Example:

```javascript
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>`

```javascript
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 with `billingDetails`.
- **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_IFRAME` message). 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](../apis/payment/pulling-funds/cards/authorizing/handling-3d-secure.md) 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/create` on the server.
- [ ] `parentOrigin` set correctly (HTTPS in production).
- [ ] SDK (`iframe.min.js`) loaded and iframe mounted with the `sessionToken`.
- [ ] `onSuccess` / `onError` (or redirects) handled on the frontend.
- [ ] Transaction **confirmed on the backend** before fulfilling the order.

***

## Full example (condensed)

**Backend (creates the session):**

```javascript
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):**

```html
<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>
```
