# Integrating Sentinel in web applications

> Embed the Sentinel iframe in your webpage, trigger a Transaction with a postMessage call, and pass the required context and reference ID.

Source: https://help.withpersona.com/articles/m5MQFn7vxjQiZ1nKpnx8e6/
Section: Sentinel > Introduction > Integrating Sentinel

This guide covers how to add the Persona Sentinel iframe to your webpage.

## Setup

Ask your Persona Account team to set up a Sentinel transaction type and a client token before you start. You need both values to build the collection URL below.

## Instructions

1.  **Load the Sentinel collection URL.**

    Embed a link to the Sentinel collection endpoint, with your client token and transaction type ID substituted as shown below.

    ```text
    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}}
    ```

    Here's an overview of parameters. Note that two are optional:

    | Parameter                     | Description                                                                                                                                                                                                                                             |
    | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `context`                     | Required. A string that represents any contextual enrichment you'd like to provide for the event, such as `onboardingEvent`.                                                                                                                            |
    | `account-reference-id`        | Required. A string parameter for including the user's reference ID. Using this parameter ensures that information from the Sentinel event is synced to [Graph](https://help.withpersona.com/articles/6atsHmQOjAswNrwMR8NY0R/), if you use it.                                       |
    | `auto-create-account`         | Optional. A 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` | Optional. An 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.                                                 |

    To verify this is working, visit the URL above. This should result in three network calls:
    - two calls to `fp-us.withpersona.com` for browser and device fingerprinting
    - one call to `withpersona.com/api/sentinel/v1/transactions` to create the Transaction

    A blank page should load. Inspect the page and check the Network tab to verify.

2.  **Create the Transaction.**

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

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

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

    If the page is embedded within an iframe, use:

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

## Recommended usage and sample implementation

Embed the Sentinel URL in an iframe, and only load the iframe and send the Transaction as needed.

```jsx
import React, { useState, useRef, useMemo, useEffect } from 'https://esm.sh/react@18'
import ReactDOM from 'https://esm.sh/react-dom@18'

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 buttonRef = 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/'
      )
    }
  }

  useEffect(() => {
    // Add a click event listener to the button that starts collection
    const buttonElement = buttonRef.current
    if (buttonElement) buttonElement.addEventListener('click', handleClick)
    return () => {
      if (buttonElement) buttonElement.removeEventListener('click', handleClick)
    }
  }, [])

  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 ref={buttonRef} disabled={!iframeLoaded || sentinelCollecting}>
        Click Me
      </button>
    </>
  )
}

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

## Other parameters

### Routing country

For end users and 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 an iframe, you can specify an additional query string parameter:

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

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

This parameter primarily affects the Create Transaction API call. The initial `/sentinel` page load may still be served from a US-based server.
