> ## Documentation Index
> Fetch the complete documentation index at: https://docs.botshield.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Widget reference

> Attributes, events, card states and failure reasons of the botshield-verify element when it offers Trusted Accounts.

Trusted Accounts uses the same `<botshield-verify>` element as BotShield Gate. The **offer** is the card that invites the person to secure their account. It is on when the gate's **Notarize account with BotShield** switch is on in the Console and the element does not carry `notarize="false"`. With the offer on and a `platform-user-ref` that is not yet secured, the element draws the **Link BotShield ID** card instead of the verify button. This page covers what is specific to that card. For everything else, see [Web component](/gate/web-component).

```html theme={null}
<script src="https://cdn.botshield.ai/sdk.js"></script>

<botshield-verify
  site-key="pk_test_..."
  scope="account-security"
  scan-mode="modal"
  platform-user-ref="usr_8f31c2d9"
  account-hint="hana@example.com"
  checkout="false"
></botshield-verify>
```

The sample needs no `notarize` attribute. The gate's switch turns the offer on.

## Attributes

| Attribute | Required | Default | Description |
| - | - | - | - |
| `site-key` | Yes | none | Your public site key, `pk_test_...` or `pk_live_...`. The page origin must be in the key's allowed origins. |
| `scope` | Yes | none | The gate **Key** from the Console, case-sensitive. It must name an active Human Gate. The API calls the same value `gate`. |
| `notarize` | No | none | `notarize="false"` turns the offer off for this placement while the gate's **Notarize account with BotShield** switch is on. The attribute cannot turn the offer on. When the switch is off, `notarize` with any other value shows the normal verify button and fires one `botshield:failure` with `notarize_not_enabled`. When the attribute is absent, the switch decides. |
| `platform-user-ref` | Yes, for the offer | none | A stable ID for the account on your platform, used to recognise a Trusted Account. Never an email address: the value must not contain `@`. BotShield stores it only as a one-way hash. |
| `account-hint` | No | none | The account the person is signed in to, for example an email address. The card shows it masked, as **Signed in · h•••@e•••.com**. The mask is applied on the page and the value is never sent to BotShield. Without it, the card shows no sign-in pill. |
| `checkout` | No | `true` | `false` hides the action button the widget draws under the card. Use `false` when the placement has nothing to submit. |
| `theme` | No | `auto` | `light`, `dark` or `auto`. `auto` follows the visitor's `prefers-color-scheme`. |
| `enroll` | No | none | Deprecated name for `notarize`. It behaves the same and logs one console warning. See [Alias window](/changelog/alias-window). |
| `link-on-verify` | No | none | Deprecated. It has no effect. See [Alias window](/changelog/alias-window). |

`state` is set by the widget: `idle`, `verifying`, `verified` or `failed`. Read it, do not write it.

### When the offer appears

| Condition | What the widget draws |
| - | - |
| Offer on, `platform-user-ref` set, account not yet secured | The **Link BotShield ID** card |
| Offer on, account already secured by this person | The normal verify button. A pass reports `trusted: true`. |
| Offer on, `platform-user-ref` missing | The normal verify button, one console warning, and one `botshield:failure` with `notarize_requires_user_ref` |
| Offer on, `platform-user-ref` contains `@` | The normal verify button and one `botshield:failure` with `notarize_ref_must_be_stable` |
| Offer on, the gate is an Age Gate | The card, until the person taps. The tap is refused, the widget fires one `botshield:failure` with `notarize_human_gate_only`, and it falls back to the normal verify button. |
| The gate's switch is on, `notarize="false"` | The normal verify button |
| The gate's switch is off, no `notarize` attribute | The normal verify button |
| The gate's switch is off, `notarize` present | The normal verify button, one console warning, and one `botshield:failure` with `notarize_not_enabled` |

In every row except the first, the widget stays usable as a gate.

## Card states

Every state is one card: a short label, a heading, a line of body text, the sign-in pill from `account-hint`, and the button.

| Card | Heading | `state` | Shown when |
| - | - | - | - |
| Offer | Secure your account with BotShield | `idle` | The account is not secured. The button reads **Link BotShield ID**, **Tap to secure this account**. |
| Waiting | Finish in BotShield | `verifying` | Desktop, after the tap opened BotShield in a new tab. |
| Scan | Scan with your phone | `verifying` | Desktop, after **Use your phone instead**. Shows the QR code, the 6-character code, and the time left. |
| Secured | Your account is secured | `verified` | The person confirmed. |
| Already secured by your BotShield ID | Already secured by your BotShield ID | `failed` | `already_trusted` |
| Already secured by another BotShield ID | Already secured by another BotShield ID | `failed` | `rebind_requires_prior_id` |
| Unavailable | We couldn't secure this account just now | `failed` | The request expired, was declined, or could not be completed. The button reads **Tap to try again** and starts a new request. |

On a phone the button opens BotShield directly. There is no QR code and no code on the card.

### The code

The code has 6 characters from the letters and digits `ABCDEFGHJKLMNPQRSTUVWXYZ23456789`. It lasts 5 minutes and works once. On a desktop the code is always shown beside the QR code, so a person whose camera cannot scan can type it at `app.botshield.ai/link`.

### The two refusals

Both refusals protect the rule of one human, one account. Neither one changes any existing binding.

| `reason` | Meaning | What your platform should do |
| - | - | - |
| `already_trusted` | This BotShield ID already secures a **different** account on your platform. It is never the result of a person returning to the account they already secured. That case shows the normal verify button and is not an error. | Tell the user that they have secured another account with you. They can unlink it in the BotShield app, then try again. Treat repeated attempts as a signal that one person holds several accounts. |
| `rebind_requires_prior_id` | This account is already secured by a different BotShield ID. | Do not replace the binding. If the account changed hands, revoke the binding in the Console registry, then let the new owner secure it. |

## Events

All events bubble and cross the Shadow DOM boundary, so you can listen on the element, a parent, or `document`.

### `botshield:success`

Fired when the person confirmed.

```json theme={null}
{
  "token": "eyJhbGciOiJFUzI1NiIsImtpZCI6...",
  "request_id": "req_4b1f0c6e2a9d4e7f8a3b5c6d7e8f9a0b",
  "score": null,
  "via": "ceremony",
  "trusted": true
}
```

| Field | Description |
| - | - |
| `token` | The signed attestation token, a JWT, or `null` when the result carried none. It is never a request ID. |
| `request_id` | The verification request, `req_...`. |
| `via` | `"ceremony"` when the person confirmed with a passkey. |
| `trusted` | `true` when the account is secured. It comes from BotShield's answer, never from the widget's own settings. When a request completes without `trusted: true`, the widget reports an ordinary verified result with `trusted: false`. |
| `score` | Always `null`. Ignore it. |

[Verify the token on your server](/trusted-accounts/token-and-webhooks) before you act on it.

### `botshield:link-code`

Fired when a code is created, for platforms that draw their own code and countdown.

```json theme={null}
{
  "code": "7HK4Q2",
  "expires_at": "2026-10-20T17:09:05.000Z",
  "request_id": "req_4b1f0c6e2a9d4e7f8a3b5c6d7e8f9a0b"
}
```

The event never carries the account hint.

### `botshield:failure`

`detail.reason` is always present. These reasons are specific to Trusted Accounts. For the reasons shared with BotShield Gate, such as `origin_not_allowed` and `gate_not_active`, see [Web component](/gate/web-component#botshieldfailure).

| `reason` | When | The widget then shows |
| - | - | - |
| `notarize_not_enabled` | The element carries `notarize`, and the gate's **Notarize account with BotShield** switch is off. Fired once. Turn the switch on in the Console. | The normal verify button |
| `notarize_requires_user_ref` | The offer is on and `platform-user-ref` is missing. Fired once. | The normal verify button |
| `notarize_ref_must_be_stable` | `platform-user-ref` contains `@`. Fired once. | The normal verify button |
| `notarize_human_gate_only` | `scope` names an Age Gate. Fired once. | The normal verify button |
| `gate_not_found` | No active gate has this `scope` key in the site key's environment. | The normal verify button |
| `site_key_required` | No site key was sent. | The normal verify button |
| `site_key_invalid` | The site key is unknown, or the page origin is not in its allowed origins. | The normal verify button |
| `already_trusted` | This BotShield ID already secures a different account on your platform. `detail` has `request_id` and `message`. | The **Already secured by your BotShield ID** card |
| `rebind_requires_prior_id` | This account is already secured by a different BotShield ID. `detail` has `request_id` and `message`. | The **Already secured by another BotShield ID** card |
| `declined` | The person selected **Cancel** in BotShield. `detail` has `request_id`. | The **Unavailable** card |
| `expired` | The person did not finish within 5 minutes. `detail` has `request_id`. | The **Unavailable** card |
| `mint_failed` | The code could not be created because BotShield could not be reached. | The **Unavailable** card |
| `unavailable` | The binding could not be recorded. Nothing was changed. | The **Unavailable** card |

An **Unavailable** result is not an accusation. It means the account was not secured this time.

### `botshield:gate-status`

Fired by the normal verify button when its pre-check answers. `botshield:census-status` and `botshield:multipass-status` are deprecated names for the same event. See [Alias window](/changelog/alias-window).

## Methods

`verify()`, `reset()` and `getToken()` work as they do for a gate. A call to `verify()` from your code is not a user gesture, so on a desktop the widget cannot open a new tab. The card goes straight to **Scan with your phone**.

## Styling

The card renders in a closed Shadow DOM. Style it through these parts:

| Part | Element |
| - | - |
| `::part(card)` | The card |
| `::part(container)` | The button |
| `::part(pill)` | The sign-in pill |
| `::part(ghost)` | The **Use your phone instead** link |
| `::part(qr)` | The QR code |
| `::part(branding)` | The BotShield mark |

The card is 520 pixels wide on a desktop and 390 pixels wide on a narrow screen.
