postMessage Protocol

📘

When to use this guide

If you're using one of the ProphetX SDKs (@prophetx/sdk-react, @prophetx/sdk-react-native, or @prophetx/sdk-js), the SDK handles all messaging for you. This guide is for partners who need direct iframe control — custom modal wrappers, non-JavaScript hosts, or advanced orchestration.

Message flow

A complete session follows this sequence:

Your page                              Embed (iframe)
    |                                       |
    |  <-- PROPHETX_EMBED_LOADED ---------- |  iframe mounted
    |                                       |
    |  --- PROPHETX_SESSION --------------> |  send JWT
    |  --- PROPHETX_THEME (optional) -----> |  send theme
    |                                       |
    |                                       |  validate token
    |                                       |  (client + server)
    |                                       |
    |  <-- PROPHETX_READY ----------------- |  session valid
    |                                       |
    |  <-- PROPHETX_RESIZE (repeated) ----- |  content height changes
    |                                       |
    |      ... user completes flow ...      |
    |                                       |
    |  <-- PROPHETX_SUCCESS --------------- |  transaction complete
    |      or PROPHETX_ERROR                |  something went wrong
    |      or PROPHETX_CANCEL               |  user abandoned flow
    |                                       |
    |  --- (remove iframe) ---              |

If the token expires mid-flow:

    |  <-- PROPHETX_SESSION_EXPIRED ------ |  JWT exp reached
    |                                      |
    |  --- PROPHETX_SESSION_REFRESH -----> |  send fresh JWT
    |                                      |
    |        ... flow resumes ...          |

Outbound messages (Embed → Your page)

These are the messages your message event listener will receive from the embed.

PROPHETX_EMBED_LOADED

The iframe has mounted and is waiting for a session token. This is always the first message the embed sends.

{ "type": "PROPHETX_EMBED_LOADED" }

When you receive this: respond immediately with PROPHETX_SESSION (and optionally PROPHETX_THEME).

PROPHETX_READY

The session token has been validated (client-side decode and server-side verification). The embed is fully initialized and the user can interact with it.

{ "type": "PROPHETX_READY" }

When you receive this: the embed is live. Show it to the user if it was hidden during loading.

PROPHETX_SUCCESS

A deposit or withdrawal completed successfully.

{
  "type": "PROPHETX_SUCCESS",
  "payload": {
    "transactionId": "tx_abc123",
    "amount": 50.00
  }
}
FieldTypeDescription
transactionIdstringUnique transaction identifier. Store this on your backend.
amountnumber | undefinedDollar amount of the transaction. May be omitted in edge cases.

When you receive this: close the embed, update your UI (refresh balance), and record the transactionId.

PROPHETX_ERROR

Something went wrong — a failed transaction, an invalid session, a geofence block, or an unexpected runtime error.

{
  "type": "PROPHETX_ERROR",
  "payload": {
    "code": "DEPOSIT_FAILED",
    "message": "The deposit could not be completed."
  }
}
FieldTypeDescription
codestringMachine-readable error code. See Error codes below.
messagestringHuman-readable description. Safe to display to the user.

When you receive this: show an appropriate error state. Some codes (like SESSION_EXPIRED) have specific handling — see the error codes table.

PROPHETX_CANCEL

The user explicitly closed or cancelled the flow (e.g. tapped a close button inside the embed).

{ "type": "PROPHETX_CANCEL" }

When you receive this: close the embed. No error occurred — the user chose to leave.

PROPHETX_RESIZE

The embed's content height changed. Sent whenever the visible content grows or shrinks — for example, when a form expands or an error banner appears.

{
  "type": "PROPHETX_RESIZE",
  "payload": {
    "height": 580
  }
}
FieldTypeDescription
heightnumberContent height in pixels. A value of 0 means the embed has no content — use a default height (420px is recommended).

When you receive this: update the iframe's height style to match. This prevents scrollbars inside the iframe and gives users a seamless experience.

PROPHETX_SESSION_EXPIRED

The JWT's exp claim has passed. The embed cannot continue until it receives a fresh token.

{ "type": "PROPHETX_SESSION_EXPIRED" }

When you receive this: fetch a new JWT from your backend and send it back via PROPHETX_SESSION_REFRESH. If you cannot provide a fresh token, remove the iframe — the embed will remain in an expired state.

PROPHETX_GEOFENCE_BLOCKED

The user's location is in a restricted state. This is a dedicated message for geofence blocks — the embed may also send PROPHETX_ERROR with code GEOFENCE_BLOCKED depending on the flow.

{
  "type": "PROPHETX_GEOFENCE_BLOCKED",
  "payload": {
    "stateCode": "NY",
    "reason": "Transactions are not available in your state."
  }
}
FieldTypeDescription
stateCodestringTwo-letter US state code where the user was detected.
reasonstringHuman-readable explanation. Safe to display.

When you receive this: show geo-specific messaging. The user cannot proceed from this state.

PROPHETX_KYC_SUCCESS

The user completed identity verification (KYC).

{
  "type": "PROPHETX_KYC_SUCCESS",
  "payload": {
    "identityUuid": "id_f47ac10b-58cc-4372-a567-0e02b2c3d479"
  }
}
FieldTypeDescription
identityUuidstringUUID of the verified identity record.

When you receive this: the user is now cleared for transactions. You can close the onboarding embed and proceed to deposit/withdraw flows.

PROPHETX_KYC_FAILED

Identity verification was rejected.

{
  "type": "PROPHETX_KYC_FAILED",
  "payload": {
    "reason": "VERIFICATION_FAILED"
  }
}
FieldTypeDescription
reasonstringMachine-readable failure reason (e.g. VERIFICATION_FAILED, DOCUMENT_REJECTED).

When you receive this: display a failure message. The user may need to contact support or retry with different documents.

PROPHETX_KYC_PENDING

The user submitted identity documents but the verification provider hasn't returned a final result yet.

{
  "type": "PROPHETX_KYC_PENDING",
  "payload": {}
}

When you receive this: show a "verification in progress" state. Poll GET private/v1/users/USERID on your backend or wait for a webhook to determine the final outcome.

PROPHETX_HEADER_UPDATE

The embed's internal header changed — the title and back-button visibility for the current screen. Sent whenever the user navigates between steps inside a flow (e.g. amount → method selection → provider screen).

{
  "type": "PROPHETX_HEADER_UPDATE",
  "payload": {
    "title": "Select payment method",
    "showBack": true
  }
}
FieldTypeDescription
titlestringTitle text the embed wants to display in its header.
showBackbooleanWhether a back affordance should be visible for this screen.

When you receive this: if you're rendering your own modal chrome around the iframe, update the title and toggle a back button accordingly. When the user taps your back button, send PROPHETX_BACK to the iframe (see below). If you let the embed render its own header, you can ignore this message — it's primarily for partners building custom chrome.

Inbound messages (Your page → Embed)

These are the messages you send to the embed iframe via iframe.contentWindow.postMessage(message, embedOrigin).

PROPHETX_SESSION

Delivers the signed JWT to the embed. Send this immediately after receiving PROPHETX_EMBED_LOADED.

{
  "type": "PROPHETX_SESSION",
  "payload": {
    "token": "eyJhbGciOiJFZERTQSIs..."
  }
}
FieldTypeDescription
tokenstringA signed JWT. See Session Token Guide for claims and signing.

The embed validates the token in two phases:

  1. Client-side — decodes the JWT and checks exp
  2. Server-side — sends the token to the ProphetX API for full validation (signature, issuer, scope)

If both pass, the embed sends PROPHETX_READY. If either fails, it sends PROPHETX_ERROR with one of: SESSION_EXPIRED, SESSION_INVALID, or ORIGIN_UNAUTHORIZED.

PROPHETX_SESSION_REFRESH

Provides a fresh JWT after the embed reports PROPHETX_SESSION_EXPIRED. Only send this in response to an expiration — never proactively.

{
  "type": "PROPHETX_SESSION_REFRESH",
  "payload": {
    "token": "eyJhbGciOiJFZERTQSIs..."
  }
}
FieldTypeDescription
tokenstringA freshly signed JWT replacing the expired one.

The embed re-validates the new token and resumes the flow if valid.

PROPHETX_THEME

Configures the embed's visual appearance. Send alongside PROPHETX_SESSION on load, or at any time to update the theme dynamically (e.g. when the user toggles dark mode in your app).

{
  "type": "PROPHETX_THEME",
  "payload": {
    "mode": "dark",
    "primaryColor": "#e91e63"
  }
}

All fields are optional. The embed merges your overrides with its default design tokens.

FieldTypeDescription
mode"light" | "dark"Base color scheme.
primaryColorstringPrimary brand color (CSS color value).
colorOverridesobjectFine-grained color token overrides. See below.
spacingOverridesobjectSpacing scale overrides (xs through xl).
radiusOverridesobjectBorder-radius overrides (sm, md, lg, full).
fontSizeOverridesobjectFont-size scale overrides (xs through xl).

All color override tokens
TokenDescription
primaryPrimary brand color
primaryHoverPrimary color on hover
primaryForegroundText on primary background
secondarySecondary brand color
secondaryHoverSecondary color on hover
backgroundPage background
surfaceCard/container background
surfaceElevatedElevated surface (modals, dropdowns)
textPrimary text color
textMutedSecondary text color
textSubtleTertiary/hint text color
borderDefault border color
borderSubtleSubtle/light border color
errorError text/icon color
errorBackgroundError banner background
successSuccess text/icon color
successBackgroundSuccess banner background
warningWarning text/icon color
warningBackgroundWarning banner background
ctaCall-to-action button background
ctaHoverCTA button hover state
ctaForegroundText on CTA buttons
filledFilled button background
filledHoverFilled button hover state
filledForegroundText on filled buttons
dangerDanger/destructive action color
dangerHoverDanger hover state
dangerForegroundText on danger buttons

PROPHETX_LOCALE

Sets the language used inside the embed. Send alongside PROPHETX_SESSION on load, or at any time to switch languages dynamically (e.g. when the user changes locale in your app).

{
  "type": "PROPHETX_LOCALE",
  "payload": {
    "locale": "es"
  }
}
FieldTypeDescription
localestringBCP 47 locale tag. Currently supported: "en" (default), "es". Unknown locales fall back to "en".

The embed swaps its UI strings as soon as the message is received. No restart or re-mount required.

PROPHETX_BACK

Tells the embed to navigate one step back in the current flow. Send this when the user taps a back button you render in your own modal chrome (in response to PROPHETX_HEADER_UPDATE with showBack: true).

{ "type": "PROPHETX_BACK" }

If the embed manages its own header (the default), you don't need to send this — the back button inside the iframe handles it directly.

Error codes

These codes appear in the code field of PROPHETX_ERROR messages.

Session errors

CodeCauseWhat to do
SESSION_EXPIREDJWT exp is in the pastFetch a fresh token and resend, or close the embed
SESSION_INVALIDMalformed JWT or server-side rejection (bad signature, wrong issuer)Check your signing implementation. Do not retry with the same token.
ORIGIN_UNAUTHORIZEDYour page's origin is not in your partner-registered allowlistRegister your origin with ProphetX. See Before you begin.

Deposit errors

CodeCauseWhat to do
DEPOSIT_FAILEDDeposit API returned an errorDisplay the message to the user. They can retry.
DEPOSIT_NOT_COMPLETEDDeposit finalization returned a non-completed statusThe deposit was initiated but did not complete. Check transaction status on your backend.
TRUSTLY_LOAD_FAILEDThe Trustly payment SDK failed to loadLikely a network issue. Retry or offer an alternative payment method.
TRUSTLY_PROVIDER_ERRORTrustly returned an error during the payment flowDisplay the message. The user can retry.
ZEROHASH_PROVIDER_ERRORZeroHash returned an errorDisplay the message. The user can retry.

Withdrawal errors

CodeCauseWhat to do
WITHDRAWAL_FAILEDWithdrawal API returned an errorDisplay the message. The user can retry.
KYC_REQUIREDUser has not completed identity verificationOpen the KYC/onboarding flow. Once verified, the user can retry the withdrawal.

Other errors

CodeCauseWhat to do
GEOFENCE_BLOCKEDUser is in a restricted US stateDisplay a location-restricted message. The user cannot proceed from this state.
UNCAUGHT_ERRORAn unexpected runtime error in the embedLog the message for debugging. Ask the user to retry or contact support.

Complete example

A minimal integration without any SDK — just raw postMessage:

<button id="open-deposit">Deposit</button>
<div id="embed-container" style="display:none"></div>

<script>
  const EMBED_URL = 'https://embed.prophetx.co/deposit';
  const embedOrigin = new URL(EMBED_URL).origin;
  let iframe;

  document.getElementById('open-deposit').addEventListener('click', async () => {
    // 1. Fetch a session token from your backend
    const res = await fetch('/api/prophetx-token');
    const { token } = await res.json();

    // 2. Create the iframe
    iframe = document.createElement('iframe');
    iframe.src = EMBED_URL;
    iframe.style.width = '100%';
    iframe.style.height = '420px';
    iframe.style.border = 'none';

    const container = document.getElementById('embed-container');
    container.innerHTML = '';
    container.style.display = 'block';
    container.appendChild(iframe);

    // 3. Listen for messages from the embed
    window.addEventListener('message', function handler(event) {
      if (event.origin !== embedOrigin) return;

      const { type, payload } = event.data;

      switch (type) {
        case 'PROPHETX_EMBED_LOADED':
          // Respond with the session token
          iframe.contentWindow.postMessage(
            { type: 'PROPHETX_SESSION', payload: { token } },
            embedOrigin
          );
          // Optionally set a theme
          iframe.contentWindow.postMessage(
            { type: 'PROPHETX_THEME', payload: { mode: 'light' } },
            embedOrigin
          );
          break;

        case 'PROPHETX_READY':
          console.log('Embed is ready');
          break;

        case 'PROPHETX_RESIZE':
          iframe.style.height = (payload.height || 420) + 'px';
          break;

        case 'PROPHETX_SUCCESS':
          console.log('Transaction complete:', payload.transactionId, payload.amount);
          cleanup(handler);
          break;

        case 'PROPHETX_ERROR':
          console.error('Error:', payload.code, payload.message);
          // Don't auto-close on SESSION_EXPIRED — refresh the token instead
          if (payload.code === 'SESSION_EXPIRED') break;
          cleanup(handler);
          break;

        case 'PROPHETX_CANCEL':
          console.log('User cancelled');
          cleanup(handler);
          break;

        case 'PROPHETX_SESSION_EXPIRED':
          // Fetch a fresh token and send it back
          fetch('/api/prophetx-token')
            .then(r => r.json())
            .then(({ token: freshToken }) => {
              iframe.contentWindow.postMessage(
                { type: 'PROPHETX_SESSION_REFRESH', payload: { token: freshToken } },
                embedOrigin
              );
            });
          break;
      }
    });
  });

  function cleanup(handler) {
    window.removeEventListener('message', handler);
    document.getElementById('embed-container').style.display = 'none';
    if (iframe) iframe.remove();
  }
</script>

Best practices

Always validate the origin. Only process messages where event.origin matches the embed URL's origin. Never use '*' as the targetOrigin when posting messages to the iframe — always use the embed's origin.

// Good
iframe.contentWindow.postMessage(message, 'https://embed.prophetx.co');

// Bad — any page could intercept this
iframe.contentWindow.postMessage(message, '*');

Handle PROPHETX_RESIZE for a smooth UX. Without it, the iframe will show internal scrollbars as content changes. Set the iframe height to payload.height on every resize event, with a minimum of 420px.

Always handle session refresh. Tokens are short-lived (5 minutes). If a user takes time filling out a form, the token will expire. Listen for PROPHETX_SESSION_EXPIRED and respond with a fresh token to avoid interrupting the user's flow.

Clean up the event listener. Remove your message event listener when the flow ends (success, error, or cancel). Stale listeners can process messages from an old iframe if the user opens a second flow.