> ## 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, chip states and failure reasons of the botshield-verify element when it offers Link with BotShield ID.

Secured Accounts uses the same `<botshield-verify>` element as BotShield Gate. With the offer on and a `platform-user-ref` that is not yet secured, the element draws the **Link with BotShield ID** chip instead of the verify button. This page covers what is specific to that chip. For everything else, see [Web component](/gate/web-component).

The widget renders the chip only. Your page owns any heading, explanation or signed-in line around it.

<Tabs>
  <Tab title="Link with BotShield ID surface">
    The surface is set up in the Console under **Secured Accounts**, then **Link with BotShield ID**. The site key finds it, so no `scope` is needed.

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

    <botshield-verify
      site-key="pk_test_..."
      notarize
      platform-user-ref="usr_8f31c2d9"
    ></botshield-verify>
    ```
  </Tab>

  <Tab title="Human Gate">
    The gate's **Link with BotShield ID** switch is on in its **Configuration** card. The switch turns the offer on, so the sample needs no `notarize` attribute.

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

    <botshield-verify
      site-key="pk_test_..."
      scope="account-security"
      platform-user-ref="usr_8f31c2d9"
    ></botshield-verify>
    ```
  </Tab>
</Tabs>

<Note>
  The 3.3 widget (`3.0.12`) is the current `/sdk.js`. A page pinned to an earlier `3.0` version labels the chip **Link BotShield ID** and can't show the Link surface without a gate. See [Widget versions](/gate/widget-versions).
</Note>

## 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. |
| `platform-user-ref` | Yes, for the offer | none | A stable ID for the account on your platform, used to recognise a Secured Account. Never an email address: the value must not contain `@`. BotShield stores it only as a one-way hash. |
| `notarize` | No | none | Asks for the offer. It never turns the offer on by itself: the Console switch decides. `notarize="false"` turns the offer off for this placement. |
| `scope` | For a Human Gate | none | The gate **Key** from the Console, case-sensitive. Leave it out on a Link with BotShield ID surface. The API calls the same value `gate`. |
| `checkout` | No | `true` | `false` hides the action button the widget draws under the chip. |
| `theme` | No | `auto` | `light`, `dark` or `auto`. `auto` follows the visitor's `prefers-color-scheme`. |
| `account-hint` | No | none | Accepted for compatibility. The widget shows nothing from it and never sends it. |
| `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 chip appears

| Condition | What the widget draws |
| - | - |
| Offer on, `platform-user-ref` set, account not secured | The **Link with BotShield ID** chip |
| Offer on, account already secured | The **Account secured** chip, once BotShield has answered. Until then it may show the normal verify button, and 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` |
| `scope` names an Age Gate | The tap is refused with `notarize_human_gate_only`, and the widget falls back to the normal verify button. |
| `notarize="false"` | The normal verify button |
| The Console 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 two, the widget stays usable as a gate.

## Chip states

Each state is the chip, a title over a short line, with at most one small link under it on a desktop.

| State | Title | Line under it | `state` | Shown when |
| - | - | - | - | - |
| Offer | Link with BotShield ID | Tap to secure this account | `idle` | The account is not secured. Desktop link: **Use your phone instead**. |
| Waiting | Link with BotShield ID | Finish in BotShield… | `verifying` | After the tap opened BotShield. Desktop link: **Didn't open? Use your phone**. |
| Scan | Link with BotShield ID | Waiting for your phone… | `verifying` | Desktop, after **Use your phone instead**. A window shows the QR code, the 6-character code and the time left. |
| Secured | Account secured | Linked to your BotShield ID | `verified` | The person confirmed, or the account was already secured. |
| Already secured | Already secured | This BotShield ID already secures an account here | `failed` | `already_trusted` |
| Held by another ID | Can't secure this account | This account is already secured by another BotShield ID | `failed` | `rebind_requires_prior_id` |
| Unavailable | Couldn't secure it just now | Tap to try again | `failed` | The request expired, was declined, or could not be completed. A tap starts a new request. |

On a phone the chip opens BotShield directly. There is no QR code and no code.

### 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 keep one BotShield ID to one account on your platform. Neither changes any existing link.

| `reason` | 3.3 name | Meaning | What your platform should do |
| - | - | - | - |
| `already_trusted` | `account_already_secured_here` | This BotShield ID already secures a **different** account on your platform. A person returning to the account they already secured never gets this. | Tell the user they have secured another account with you. They can unlink it in the BotShield app, then try again. |
| `rebind_requires_prior_id` | `account_held_by_another_id` | This account is already secured by a different BotShield ID. | Do not replace the link. If the account changed hands, revoke the link in the Console registry, then let the new owner secure it. |

`botshield:failure.reason` carries whichever name BotShield sent. Handle both.

## 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. A request that completes without `trusted: true` is reported as an ordinary verified result with `trusted: false`. |
| `verdict` | Optional. `"pass"`, when BotShield's answer carried it. |
| `score` | Always `null`. Ignore it. |

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

### `botshield:presence`

Fired when BotShield's answer about the account changes: whether it is secured right now, with no tap. The detail is `{ state, fresh_until }`, and `state: "none"` means not, or no longer, secured. See [Staying in sync](/secured-accounts/staying-in-sync).

### `botshield:presence-error`

Fired once, with `{ reason: "stream_unavailable" }`, when the widget cannot hold its live connection. The widget then asks every 60 seconds instead.

### `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"
}
```

### `botshield:failure`

`detail.reason` is always present. These reasons are specific to Secured 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 |
| - | - | - |
| `link_not_enabled` | No gate is named and the site key's Link with BotShield ID surface is off. Turn it on in the Console. | The normal verify button |
| `notarize_not_enabled` | The element names a gate whose **Link with BotShield ID** switch is off. | 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. | 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` or `account_already_secured_here` | This BotShield ID already secures a different account on your platform. `detail` has `request_id` and `message`. | The **Already secured** chip |
| `rebind_requires_prior_id` or `account_held_by_another_id` | This account is already secured by a different BotShield ID. `detail` has `request_id` and `message`. | The **Can't secure this account** chip |
| `declined` | The person selected **Cancel** in BotShield. `detail` has `request_id`. | The **Unavailable** chip |
| `expired` | The person did not finish within 5 minutes. `detail` has `request_id`. | The **Unavailable** chip |
| `mint_failed` | The code could not be created because BotShield could not be reached. | The **Unavailable** chip |
| `unavailable` | The link could not be recorded. Nothing was changed. | The **Unavailable** chip |

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

## 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. It goes straight to the QR code and the code.

## Styling

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

| Part | Element |
| - | - |
| `::part(container)` | The chip |
| `::part(branding)` | The BotShield mark on the chip |
| `::part(ghost)` | The small link under the chip, such as **Use your phone instead** |
| `::part(checkout)` | The action button under the chip |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.