> This page is for version 2020-05-18.
> For other versions, use one of these documentation indexes:
> - 2026-09-29 (default): https://docs.withpersona.com/2026-09-29/llms.txt
> - 2025-12-08: https://docs.withpersona.com/2025-12-08/llms.txt
> - 2025-10-27: https://docs.withpersona.com/2025-10-27/llms.txt
> - 2023-01-05: https://docs.withpersona.com/2023-01-05/llms.txt
> - 2022-09-01: https://docs.withpersona.com/2022-09-01/llms.txt
> - 2021-07-05: https://docs.withpersona.com/2021-07-05/llms.txt
> - 2021-05-14: https://docs.withpersona.com/2021-05-14/llms.txt
> - 2020-05-18: https://docs.withpersona.com/2020-05-18/llms.txt

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

# Sentinel Web Integration Guide

This guide covers how to add Persona Sentinel iFrame in your webpage.

## Integration

### Setup

Ask your Persona Account Team to set up a Sentinel transaction type and client token.

### Instructions

#### 1. Load Sentinel Collection URL

Embed a link to the Sentinel collection endpoint, with your client token and transaction type ID substituted as shown below. The following optional parameters are supported:

* `context` is a string that represents any contextual enrichment you'd like to provide for the event such as "onboardingEvent" etc.
* `account-reference-id` is a string parameter for including the user's reference ID. Usage of this parameter will ensure that information from the Sentinel event is synced to Graph, if being used.
* `auto-create-account` is an optional boolean string (`true` or `false`) that controls whether an account is auto-created when one does not already exist for the given `account-reference-id`. Set to `false` to opt out of auto-creation; any other value is treated as `true`.
* `auto-create-account-type-id` is an optional account type token (prefix `acttp_`) that specifies the account type to use when auto-creating an account. If omitted, defaults to the transaction type's configured default account type.
* `identifiers` is an optional array parameter for including identifiers that are sent with the transaction when collection is triggered. Add values to the array by appending `&identifiers[<index>]=<value>` to the Sentinel collection URL (e.g. `&identifiers[0]=...&identifiers[1]=...`), with the index starting at 0.

**`jsx`**

```jsx jsx
https://withpersona.com/sentinel?client-token={{CLIENT_TOKEN}}&transaction-type-id={{TXN_TYPE_ID}}&context={{CONTEXT}}&account-reference-id={{REFERENCE_ID}}&auto-create-account=true&auto-create-account-type-id={{ACCOUNT_TYPE_ID}}
```

To verify this is working, you visit the URL above. This should result in 3 network calls

* two calls to [`fp-us.withpersona.com`](http://fp-us.withpersona.com) for Browser and Device fingerprinting
* one call to [withpersona.com/api/sentinel/v1/transactions](http://withpersona.com/api/sentinel/v1/transactions) to create the transaction

A blank page should load (Inspect Page → Network tab to verify).

#### 2. Create the transaction

To trigger Transaction creation, which sends the device information, context, identifiers, etc. to Persona, *post a message* to the content window containing the `sentinel` page.

If the page was opened directly in the main window, open console and use:

**`jsx`**

```jsx jsx
window.postMessage({'type':'persona/passive/collect', 'origin':'https://withpersona.com'})
```

If the page is embedded within the iframe, use:

**`jsx`**

```jsx jsx
document.getElementById('{{MY_IFRAME_ID}}').contentWindow.postMessage(
  {'type':'persona/passive/collect', 'origin':'{{MY_ORIGIN}}'},
  'https://withpersona.com'
);
```

### Recommended Usage & Sample Implementation

Embed the Sentinel URL in an iFrame, only load the iFrame and send the transaction as needed.

**`jsx`**

```jsx jsx
import React, {
  useState,
  useRef,
  useMemo,
  useEffect
} from "https://esm.sh/react@18";
import ReactDOM from "https://esm.sh/react-dom@18";
import TypesReact from "https://esm.sh/@types/react";
import TypesReactDom from "https://esm.sh/@types/react-dom";
function SentinelContainer() {
  const [iframeLoaded, setIframeLoaded] = useState(false);
  const [sentinelCollecting, setSentinelCollecting] = useState(false);
  // To store the callback result from Sentinel
  const [postResult, setPostResult] = useState(null);
  const iframeRef = useRef(null);
  const memoizedIframe = useMemo(
    () => (
      <iframe
        style={{ display: "none" }}
        ref={iframeRef}
        id="sentinel"
        src="https://withpersona.com/sentinel?client-token={CLIENT_TOKEN}&transaction-type-id=	{TXN_TYPE_ID}&context=referenceImplementation&account-reference-id={REFERENCE_ID}"
      />
    ),
    []
  );

  const handleIframeLoad = () => {
    setIframeLoaded(true);
  };

  const handleMessage = (message) => {
    if (message.origin === "https://withpersona.com") {
      setPostResult(message.data);
      setSentinelCollecting(false);
    }
  };

  useEffect(() => {
    // Add a message event listener to your application to handle postMessage callbacks
    window.addEventListener("message", handleMessage);
    const iframeElement = iframeRef.current;
    if (iframeElement) {
      // Add a load event listener to the iframe to detect when it has finished loading
      iframeElement.addEventListener("load", handleIframeLoad);
    }
    // Return value is used to provide a cleanup function for React's useEffect
    return () => {
      window.removeEventListener("message", handleMessage);
      if (iframeElement) {
        iframeElement.removeEventListener("load", handleIframeLoad);
      }
    };
  }, []);

  const handleClick = () => {
    if (iframeRef.current && iframeRef.current.contentWindow) {
      setSentinelCollecting(true);
      // Post a message to the Sentinel iframe to begin collection
      iframeRef.current.contentWindow.postMessage(
        // The origin refers to the domain in which the iframe is being embedded
        { type: "persona/passive/collect", origin: "https://mywebpage.com" },
        // The second parameter is the target origin, this should be https://withpersona.com
        // (or the domain matching the Sentinel endpoint being used for the iframe)
        "https://withpersona.com/"
      );
    }
  };

  return (
    <>
      {memoizedIframe}
      <h1>Hello World</h1>
      <p>Iframe loaded: {iframeLoaded.toString()}</p>
      <p>Sentinel result: {sentinelCollecting ? "Loading..." : ""}</p>
      <pre>
        {postResult ? JSON.stringify(postResult, null, 2) : "No data available"}
      </pre>
      <button
        onClick={handleClick}
        disabled={!iframeLoaded || sentinelCollecting}
      >
        Click Me
      </button>
    </>
  );
}

// Tells React to attach the HelloWorld component to the 'root' HTML div
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<SentinelContainer />);
// ReactDOM.render(<SentinelContainer />, document.getElementById("root"));
```

#### Other parameters

**Routing country**

For end users & customers on the DE shard, taking advantage of geographical routing may be desired to reduce client-facing latencies. Since the /sentinel endpoint is fetched via a GET request (when loaded via iframe), one can specify an additional query string parameter:

**`jsx`**

```jsx jsx
https://withpersona.com/sentinel?client-token={{CLIENT_TOKEN}}&transaction-type-id={{TXN_TYPE_ID}}&routing-country=US
```

One can specify either `US` or `DE` for the `routing-country` query string parameter. Subsequent API calls made to Persona will be routed accordingly.

Note that this parameter will primarily affect the `Create Transaction` API call. The initial `/sentinel` page load may still be served from a US-based server.