> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://partner.ninjatrader.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://partner.ninjatrader.com/_mcp/server.

# Embedding the Funding Page with an iframe

You can embed the NinjaTrader funding page inside your own web application, so a user adds funds without leaving your product. This page covers what your page needs to do. Unlike the [ShortGrantCode handoff](/connect/overview/partner-integration/sso-with-short-grant-code), which redirects the browser to a NinjaTrader-hosted page, this flow keeps the user on your page and passes an access token to the embedded frame.

## Prerequisites

NinjaTrader provisions and shares three things with you:

* The **iframe host URL**, which serves the `/transfer-funds` route.
* The **token endpoint**, which your backend calls with the `jwt-bearer` grant to obtain a short-lived access token. See [Partner Token](/connect/overview/partner-integration/partner-token) for the assertion format, claims, and JWKS requirements.
* The **allowed parent origins**, meaning the exact scheme, host, and port of every page that will host the iframe. Inbound messages your page sends to the frame are dropped if they come from any other origin.

Register your hosting origin before you integrate. If it is missing, the iframe still loads and still emits its outbound messages, but the inbound channel — messages your page sends to the frame — fails silently.

**Warning:** The per-partner origin allowlist is not yet wired to the backend configuration, which has two consequences today. Outbound messages from the iframe are posted with a wildcard target origin rather than your registered origin. They carry no sensitive data, but you must still validate `event.origin` on your side. Inbound messages you send to the iframe are ignored until the allowlist ships, because the frame does not register its parent-message listener without a configured allowlist. Build the listener now and defer relying on the inbound channel until NinjaTrader confirms the allowlist is live for your integration.

Your users must already have NinjaTrader accounts in the organization your partner configuration is bound to. Accounts are never auto-provisioned, and a user resolved outside that organization is rejected at the token endpoint. Coordinate provisioning before go-live.

## Obtaining an Access Token

For each user session, your backend calls the token endpoint and returns a short-lived access token to your frontend. Treat it as a bearer credential:

* Fetch it server side. Never put long-lived secrets in the browser.
* Fetch a fresh token every time you mount the iframe.
* Do not log it, store it in `localStorage`, or include it in analytics events.

## Mounting the iframe

Pass the token in the URL fragment, never the query string. Fragments are not sent to servers, so the token stays out of access logs and referrer headers.

```html
<iframe
  id="nt-funding"
  src="https://FUNDING_HOST/transfer-funds#token=ACCESS_TOKEN"
  style="width: 100%; border: 0;"
  allow="payment"
  title="Funding"
></iframe>
```

Only the `/transfer-funds` route is embeddable. Navigating the frame to any other route is blocked and raises a `route_blocked` error to your page. Once the frame loads, the token is consumed and removed from the URL, so you do not need to scrub it yourself.

## Receiving Messages

The frame communicates with `window.postMessage`. Always validate `event.origin` against the NinjaTrader funding host before acting on a message, and confirm the message came from your frame.

```js
const NT_ORIGIN = 'https://FUNDING_HOST';
const iframe = document.getElementById('nt-funding');

window.addEventListener('message', (event) => {
  if (event.origin !== NT_ORIGIN) return;
  if (event.source !== iframe.contentWindow) return;

  const msg = event.data;
  switch (msg?.type) {
    case 'auth_ready':
      // Session established. Safe to reveal the UI.
      break;

    case 'resize':
      iframe.style.height = msg.height + 'px';
      break;

    case 'error':
      console.error('NinjaTrader iframe error', msg);
      break;

    default:
      // Unknown types ship in future protocol versions. Log and ignore.
      break;
  }
});
```

| `type`       | Payload                                                      | Purpose                                                     |
| ------------ | ------------------------------------------------------------ | ----------------------------------------------------------- |
| `auth_ready` | `{ partnerOrgName?: string }`                                | The session is established and the frame is safe to show    |
| `resize`     | `{ height: number }`                                         | Content height changed, so resize the frame to match        |
| `error`      | `{ code: string, message?: string, correlationId?: string }` | Something went wrong, to surface or log per your own design |

The set of message types is extensible, so keep a `default` arm that logs and ignores anything unrecognized.

### Error Codes

| `code`            | Meaning                                                            |
| ----------------- | ------------------------------------------------------------------ |
| `no_token`        | No access token was found in the URL fragment                      |
| `session_expired` | The partner session expired and the refresh token was rejected     |
| `unverified`      | The user has not completed verification                            |
| `route_blocked`   | A navigation to a route other than `/transfer-funds` was attempted |
| `sim_only_user`   | The user is not eligible for the funding flow                      |
| `no_live_account` | The user has no eligible live account for funding                  |
| `closed_account`  | The account is closed, so this surface is unavailable              |

Treat any unrecognized code defensively by logging it and recovering as described below. Origin mismatches never arrive as an `error` message, because inbound messages from an unregistered origin are dropped silently.

## Sending Messages

You can post messages to the frame. Always pass the NinjaTrader origin as the second argument rather than a wildcard.

```js
iframe.contentWindow.postMessage(
  {
    type: 'handshake',
    partnerName: 'Sample Brokerage',
    partnerId: 'samplepartner',
    theme: { primary: '#0055aa' },
    protocolVersion: 1
  },
  NT_ORIGIN
);
```

| `type`      | Payload                                                  | Purpose                                                                      |
| ----------- | -------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `handshake` | `{ partnerName, partnerId, theme?, protocolVersion: 1 }` | Send the protocol version and optional theme overrides after the frame loads |
| `close`     | none                                                     | Signal that the user closed the funding flow on your side                    |

`partnerName` and `partnerId` are required and `theme` is optional. A handshake carrying any `protocolVersion` other than `1` is ignored. Sending the handshake is recommended for forward compatibility, but as noted above the inbound channel is inactive until the allowlist ships.

## Sizing

The frame does not scroll itself in embed mode, so your page is responsible for sizing it. Start at a sensible default height such as 600 pixels and resize on each `resize` message. Avoid a percentage height unless the frame sits inside a fixed-height container.

## Recovering from Errors

When the frame cannot establish a session, whether the token expired, the token was invalid, or the origin is not allowed, you receive an `error` message. Recovery is usually to discard the frame, fetch a fresh access token from your backend, and mount it again. If errors persist, contact NinjaTrader support with the error `code` and a timestamp.

## Next Steps

* [Partner Token](/connect/overview/partner-integration/partner-token): sign the assertion and exchange it for the access token this flow needs.
* [SSO with ShortGrantCode](/connect/overview/partner-integration/sso-with-short-grant-code): the redirect-based alternative, for sending a user to a full NinjaTrader-hosted page.