postMessage Protocol
When to use this guideIf 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
}
}| Field | Type | Description |
|---|---|---|
transactionId | string | Unique transaction identifier. Store this on your backend. |
amount | number | undefined | Dollar 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."
}
}| Field | Type | Description |
|---|---|---|
code | string | Machine-readable error code. See Error codes below. |
message | string | Human-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
}
}| Field | Type | Description |
|---|---|---|
height | number | Content 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."
}
}| Field | Type | Description |
|---|---|---|
stateCode | string | Two-letter US state code where the user was detected. |
reason | string | Human-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"
}
}| Field | Type | Description |
|---|---|---|
identityUuid | string | UUID 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"
}
}| Field | Type | Description |
|---|---|---|
reason | string | Machine-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
}
}| Field | Type | Description |
|---|---|---|
title | string | Title text the embed wants to display in its header. |
showBack | boolean | Whether 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..."
}
}| Field | Type | Description |
|---|---|---|
token | string | A signed JWT. See Session Token Guide for claims and signing. |
The embed validates the token in two phases:
- Client-side — decodes the JWT and checks
exp - 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..."
}
}| Field | Type | Description |
|---|---|---|
token | string | A 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.
| Field | Type | Description |
|---|---|---|
mode | "light" | "dark" | Base color scheme. |
primaryColor | string | Primary brand color (CSS color value). |
colorOverrides | object | Fine-grained color token overrides. See below. |
spacingOverrides | object | Spacing scale overrides (xs through xl). |
radiusOverrides | object | Border-radius overrides (sm, md, lg, full). |
fontSizeOverrides | object | Font-size scale overrides (xs through xl). |
All color override tokens
| Token | Description |
|---|---|
primary | Primary brand color |
primaryHover | Primary color on hover |
primaryForeground | Text on primary background |
secondary | Secondary brand color |
secondaryHover | Secondary color on hover |
background | Page background |
surface | Card/container background |
surfaceElevated | Elevated surface (modals, dropdowns) |
text | Primary text color |
textMuted | Secondary text color |
textSubtle | Tertiary/hint text color |
border | Default border color |
borderSubtle | Subtle/light border color |
error | Error text/icon color |
errorBackground | Error banner background |
success | Success text/icon color |
successBackground | Success banner background |
warning | Warning text/icon color |
warningBackground | Warning banner background |
cta | Call-to-action button background |
ctaHover | CTA button hover state |
ctaForeground | Text on CTA buttons |
filled | Filled button background |
filledHover | Filled button hover state |
filledForeground | Text on filled buttons |
danger | Danger/destructive action color |
dangerHover | Danger hover state |
dangerForeground | Text 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"
}
}| Field | Type | Description |
|---|---|---|
locale | string | BCP 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
| Code | Cause | What to do |
|---|---|---|
SESSION_EXPIRED | JWT exp is in the past | Fetch a fresh token and resend, or close the embed |
SESSION_INVALID | Malformed JWT or server-side rejection (bad signature, wrong issuer) | Check your signing implementation. Do not retry with the same token. |
ORIGIN_UNAUTHORIZED | Your page's origin is not in your partner-registered allowlist | Register your origin with ProphetX. See Before you begin. |
Deposit errors
| Code | Cause | What to do |
|---|---|---|
DEPOSIT_FAILED | Deposit API returned an error | Display the message to the user. They can retry. |
DEPOSIT_NOT_COMPLETED | Deposit finalization returned a non-completed status | The deposit was initiated but did not complete. Check transaction status on your backend. |
TRUSTLY_LOAD_FAILED | The Trustly payment SDK failed to load | Likely a network issue. Retry or offer an alternative payment method. |
TRUSTLY_PROVIDER_ERROR | Trustly returned an error during the payment flow | Display the message. The user can retry. |
ZEROHASH_PROVIDER_ERROR | ZeroHash returned an error | Display the message. The user can retry. |
Withdrawal errors
| Code | Cause | What to do |
|---|---|---|
WITHDRAWAL_FAILED | Withdrawal API returned an error | Display the message. The user can retry. |
KYC_REQUIRED | User has not completed identity verification | Open the KYC/onboarding flow. Once verified, the user can retry the withdrawal. |
Other errors
| Code | Cause | What to do |
|---|---|---|
GEOFENCE_BLOCKED | User is in a restricted US state | Display a location-restricted message. The user cannot proceed from this state. |
UNCAUGHT_ERROR | An unexpected runtime error in the embed | Log 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.
Updated 6 days ago
