> For the complete documentation index, see [llms.txt](https://docs.omni.integratedcommerce.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.omni.integratedcommerce.io/hosted-payment-form/best-practices.md).

# Implementation best practices

Origin validation, session timeouts, error handling, and responsive layout.

## Implement proper origin validation

Always validate that `postMessage` events come from the form's expected origin to prevent malicious scripts from sending fake transaction results.

The form is served from a different host than the API. The session URL returned when you create the session (for example `https://hpf.integratedcommerce.io/session/01JSFAMY0AGW27QKP30C727512`) is the authoritative source for the origin to expect - derive it from that URL rather than hard-coding a host, so the same code works in sandbox and production.

```javascript
// Derive the expected origin from the session URL the API returned.
const expectedOrigin = new URL(session.iframeUrl).origin;

const paymentFrame = document.querySelector("#payment-frame");

window.addEventListener("message", (event) => {
  if (event.origin !== expectedOrigin) return;
  if (!paymentFrame || event.source !== paymentFrame.contentWindow) return;
  if (event.data?.type !== "hostedPaymentFormResult") return;
  const result = event.data.data;
  if (!result || typeof result !== "object") return;
  handleTransactionResult(result);
});
```

{% hint style="warning" %}
Compare `event.origin` with a strict equality check against a full origin. Do not use `endsWith`, `includes`, or a regular expression on the origin - `https://hpf.integratedcommerce.io.attacker.example` ends with nothing suspicious to a substring test.
{% endhint %}

## Handle timeouts gracefully

Sessions expire 10 minutes after creation, at the returned `expirationTimestamp`. Customer activity does not extend the session. Handle the error channel for your integration; the code can differ between a failed session load and an expired active session. A browser redirect or callback is not guaranteed if the page is closed.

The SDK session can be loaded once. Opening it again, refreshing the page, or mounting the same `sessionId` twice can fail even before expiry. Before creating a replacement session, reconcile any earlier payment submission. A new session-creation request returns a new iframe URL and `sessionId`.

## Handle every response status

Your implementation should handle various response statuses, including:

* Successful payments (`resultCode = 0`)
* Declined transactions
* Expired sessions
* Processing errors

{% hint style="danger" %}
When processing errors occur or transaction status is uncertain, use the `GET /transactions` (List Transactions) endpoint to verify the final transaction outcome before retrying or taking corrective action. See [Retrieving transactions](/payments/transaction-retrieval.md).
{% endhint %}

## Verify the redirect hash

If you use `returnUrl`, verify the HMAC hash on every redirect before acting on the parameters. See [Verifying the redirect](/hosted-payment-form/redirect-verification.md).

## Test with the test cards

Use the published test cards during your integration development. The set includes approved cards plus cards that produce specific declines and processing errors, so you can exercise your error paths rather than only the happy path. See [Test cards](/going-live/test-cards.md).

## Responsive design

Ensure your payment page layout accommodates the iframe appropriately on all devices. The form itself is responsive and works on mobile, tablet, and desktop.

## Before you go live

Certification requires two example transactions per applicable scenario, plus evidence of your success, decline, and error messaging. Building those paths now saves a round trip later - see [Certification](/going-live/go-live.md).
