# Web Integration

Load the session URL in an iframe and receive widget events with
`window.postMessage`.

## Load the iframe

Set the iframe `src` to the `session_url` from the create-session response.
Permit camera access:

```html
<iframe
  src="{{session_url}}"
  allow="camera"
  style="width: 100%; height: 100%; border: 0"
></iframe>
```

The merchant origin must be in the Privy allowlist.

## Receive widget events

The widget sends events with the `privy:` prefix. The
[user journey](/digital-id/concepts/user-journey) shows the step that sends
each event.

| Event | Payload | Condition |
| --- | --- | --- |
| `privy:ready` | `{ session_id, state }` | The widget completed startup. `state` identifies the current widget step. |
| `privy:success` | `{ session_id, exchange_code, token?, sections? }` | Verification and consent succeeded. |
| `privy:cancel` | `{ reason }` | The user canceled or rejected the consent request. |
| `privy:error` | `{ code }` | The session is blocked, expired, or not permitted for the embedder. The session also expires when the consent confirmation window ends. |

Treat the `privy:cancel` `reason` value as opaque.

Validate `event.origin` before you read the payload. This example uses the
sandbox widget origin:

```js
window.addEventListener("message", (event) => {
  if (event.origin !== "https://stg-digitalid.privy.id") return;

  const { type, payload } = event.data || {};
  switch (type) {
    case "privy:ready":
      break;
    case "privy:success":
      sendExchangeCodeToBackend(payload.exchange_code);
      break;
    case "privy:cancel":
      break;
    case "privy:error":
      break;
  }
});
```

> Privy sends data and tokens only to a verified embedder. An unknown host
> receives `{ type }` without a payload.

Widget events are front-channel notifications for the user interface. They do
not query the backend session status.

## Shared-data preview

The optional `sections` field contains populated fields that the user agreed
to share:

```json
{
  "sections": [
    {
      "section": "my_contact_data",
      "label": "My Contact Data",
      "fields": [
        {
          "key": "email",
          "label": "Email",
          "value": "user@example.com",
          "is_verified": true
        }
      ]
    }
  ]
}
```

Use `sections` only for a user-interface preview. Get the authoritative data
from the exchange endpoint or the server-to-server callback.

## Callback

Privy also sends the verified result to the configured merchant `callback_url`.
See [Server Callback](/digital-id/guides/server-callback).
