Inyo

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:

CredentialDescription
OAUTH_CLIENT_IDIdentifier for your tenant (client).
OAUTH_CLIENT_SECRETSecret used to authenticate session creation.
Backend URLSessions API endpoint (referred to below as https://{HPP_API}).
Frontend URLOrigin 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

ParameterTypeRequiredDescription
amountNumberYesTransaction amount (e.g. 50.00). Use 0 for card_validation.
currencyStringYesCurrency code (currently only USD).
transactionTypeStringYespayment, card_validation, or account_validation.
parentOriginStringYesHTTPS origin of the page that hosts the iframe (e.g. https://your-site.com).
captureBooleanNotrue captures immediately; false only authorizes (default).
tokenTypeStringNoone_time (default) or recurring.
customCssStringNoURL of a hosted CSS file to customize the look.
billingPreferenceStringNoBilling fields visibility: editable (default), read_only, or hidden.
billingDetailsObjectNoPre-fills the billing data (name, email, address, etc.).
threeDSDataObjectNo3D Secure configuration: { "enable": true, "successUrl": "...", "failUrl": "..." }.
metadataObjectNoFree-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 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.

<!-- 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

OptionDescription
sessionTokenRequired. Token returned in Step 1.
onSuccess(data)Success callback. Receives { transaction }.
onError(error)Error/decline callback.
storeLaterUsetrue to show the "save card for future use" option.
threeDSData3D 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:

{
  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

StatusMeaning
AUTHORIZEDAuthorized (not yet captured).
APPROVED / CAPTUREDPayment completed.
DECLINEDCard declined.
CHALLENGERequires 3D Secure verification (handled automatically by the iframe).
PENDINGAsynchronous 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 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 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.

DirectionMessage typePayload
Parent → iframeINIT_SESSION{ sessionToken }
iframe → ParentRESIZE_IFRAME{ height }
iframe → ParentPAYMENT_SUCCESS{ transaction } → fires onSuccess
iframe → ParentPAYMENT_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):

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>