Skip to main content
This quickstart shows a minimal working integration for the Offline iFrame viewer:
  1. Render an <iframe> (using a placeholder bundle URL)
  2. Fetch the original file and analysis results from your backend
  3. Send them to the iframe via postMessage()
The Offline iFrame bundle URL is provided by Resistant AI during enablement.
This page uses placeholders so the viewer cannot be launched without coordination.
See also: Access and hosting for delivery models (self-hosted vs Resistant AI–hosted static bundle).

Prerequisites

Before you start, you need:
  • An Offline iFrame bundle URL provided by Resistant AI:
    • <OFFLINE_IFRAME_URL_PROVIDED_BY_RESISTANT_AI>
  • The expected iframe origin (for postMessage targetOrigin):
    • <OFFLINE_IFRAME_ORIGIN_PROVIDED_BY_RESISTANT_AI>
  • A submission_id for a document already processed via the Documents API
  • Your service can fetch:
    • the original file bytes (as ArrayBuffer)
    • API results (/fraud required; /decision and /classification optional)
If your security posture allows it, we recommend using the Resistant AI–hosted static bundle to reduce maintenance overhead (updates are managed as part of our release process). For restricted environments, self-host the bundle.

Quick start

Security and CSP notes (important)

This viewer does not call the Documents API. Your application must fetch results and the original file bytes, then send them to the iframe. This example uses:
  • an inline <script> block, and
  • an iframe load handler for initialization.
If your Content Security Policy (CSP) forbids inline scripts (script-src 'unsafe-inline'), move the JavaScript into a separate file and attach the load handler with addEventListener.
Never use * as the targetOrigin in production. Always set targetOrigin to the expected iframe origin.

Display a submission once the iframe is loaded

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Resistant AI Offline iFrame Demo</title>
  </head>

  <body>
    <iframe
      id="rai-offline-iframe"
      src="<OFFLINE_IFRAME_URL_PROVIDED_BY_RESISTANT_AI>"
      allow="clipboard-read; clipboard-write"
      width="1250"
      height="750"
      style="border: 0;"
    ></iframe>

    <script>
      async function sendToIframe() {
        const iframe = document.getElementById("rai-offline-iframe");

        // Identifier returned by the Documents API after creating a submission
        const submission_id = "YOUR_DOCUMENT_SUBMISSION_ID";

        // Example: fetch fraud result (recommended with metadata for overlays)
        // const fraudResult = await fetch(`/v2/submission/${submission_id}/fraud?with_metadata=true`).then(r => r.json());
        const fraud = JSON.stringify(fraudResult);

        // Example: fetch original uploaded file bytes
        // const fileResp = await fetch(`/files/${submission_id}`);
        const fileData = await fileResp.arrayBuffer();

        // Send message using postMessage with transferable ArrayBuffer (recommended)
        iframe.contentWindow.postMessage(
          {
            submissionId: submission_id, // for display/debugging
            fraud,
            fileData,
          },
          "<OFFLINE_IFRAME_ORIGIN_PROVIDED_BY_RESISTANT_AI>",
          [fileData]
        );
      }

      // Ensure iframe is loaded before sending message
      window.addEventListener("load", () => {
        const iframe = document.getElementById("rai-offline-iframe");
        iframe.addEventListener("load", () => {
          sendToIframe().catch(console.error);
        });
      });
    </script>
  </body>
</html>

Communication protocol

Use window.postMessage() to send analysis data to the Offline iFrame viewer.
const message = {
  // Optional but recommended (display/debugging)
  submissionId: "YOUR_DOCUMENT_SUBMISSION_ID",

  // Required
  fraud: JSON.stringify(responseFromGetFraudAPI),

  // Optional
  decision: responseFromGetDecisionAPI
    ? JSON.stringify(responseFromGetDecisionAPI)
    : undefined,
  classification: responseFromGetClassificationAPI
    ? JSON.stringify(responseFromGetClassificationAPI)
    : undefined,

  // Required (ArrayBuffer)
  fileData: fileArrayBufferAssociatedWithSubmission,

  // Optional (stringified JSON)
  style: JSON.stringify({
    colors: DEFAULT_COLORS,
  }),

  // Optional
  locale: "en",
};

document.getElementById("rai-offline-iframe").contentWindow.postMessage(
  message,
  "<OFFLINE_IFRAME_ORIGIN_PROVIDED_BY_RESISTANT_AI>",
  [message.fileData]
);

Message parameters

  • submissionId (optional, recommended): identifier used for display/debugging in the viewer. Use the API’s submission_id value.
  • fraud (required): JSON.stringify() of the response from GET /v2/submission/{submission_id}/fraud
    • Required for overlays/highlights: request fraud with ?with_metadata=true
  • decision (optional): JSON.stringify() of the response from GET /v2/submission/{submission_id}/decision
  • classification (optional): JSON.stringify() of the response from GET /v2/submission/{submission_id}/classification
  • fileData (required): ArrayBuffer of the original uploaded file
  • style (optional): JSON.stringify() of a style object (you can pass a partial override)
  • locale (optional): "en" or "pt" (ISO 639-1 language code)

Styling

If no style is passed, the UI uses a default color scheme. You can override all or part of the palette.
const DEFAULT_COLORS = {
  primary: "#5890fe",
  error: "#e44356",
  background: {
    primary: "#ffffff",
    secondary: "#fbfbfb",
  },
  text: {
    primary: "#0e153c",
    secondary: "#62666e",
  },
  border: {
    primary: "#e6e6e6",
  },
  metadata: {
    background: "#f7f8f9",
    text: {
      primary: "#6b7280",
      secondary: "#62666e",
    },
  },
  pdfViewer: {
    background: "#dcdee4",
    scrollbar: {
      track: "#c5c6cd",
      thumb: "#a7aab1",
      indicator: "#858d95",
      indicatorShown: "#e44356",
    },
  },
  scrollbar: {
    track: "#dcdee4",
    thumb: "#b9bbcb",
    thumbHover: "#b9bbcb",
  },
  documentType: {
    trusted: "#00be90",
    normal: "#5890fe",
    warning: "#ffb646",
    highRisk: "#e44356",
    failed: "#0c0a2c",
    approved: "#00be90",
    declined: "#e44356",
    manualReview: "#ffb646",
  },
  icon: {
    primary: "#0e153c",
    disabled: "#C8CED6",
  },
};

Locale

If no locale is passed, "en" is used.
const SUPPORTED_LOCALES = {
  en: "en",
  pt: "pt",
};
Example: override primary color and locale:
document.getElementById("rai-offline-iframe").contentWindow.postMessage(
  {
    style: JSON.stringify({
      colors: {
        primary: "blue",
      },
    }),
    locale: "en",
  },
  "<OFFLINE_IFRAME_ORIGIN_PROVIDED_BY_RESISTANT_AI>"
);

Implementation notes

Ensure the iframe loads before sending a message

Messages sent before the iframe is fully loaded may not be received.
  • Prefer attaching a load event handler to the iframe (as shown in the example).
  • If your application has complex routing, ensure you resend on re-mount when needed.
  • Width: 1250 px (recommended), 1000 px (minimum)
  • Height: 750 px (recommended), 600 px (minimum)

Use transferable objects for fileData

Use the transferable object pattern when sending fileData to improve performance:
  • pass [message.fileData] as the third argument to postMessage()

Debugging tips

If nothing appears or you see an error screen after sending the message:
  • Check the browser console for errors
  • Ensure all required fields (fraud, fileData) are present and correctly formatted
  • Confirm the iframe URL origin matches the postMessage target origin
  • If using a strict CSP, ensure scripts are allowed (avoid inline scripts in production)

Browser compatibility

BrowserSupported versions
Chrome100+
Firefox99+
Safari15.4+
Edge100+