Skip to main content
<botshield-verify> is a custom element that draws the Verify Human button on your page, runs the verification, and reports the result to your code. It renders in a closed Shadow DOM, so your page styles do not leak in. You need a site key and an active gate first: see Place a gate.

Install

Load the script once per page, then add the element where the button should appear.
scan-mode="modal" names the widget’s flow: a QR code modal on desktop and a hand-off to the BotShield app on mobile. It is the default, so the widget runs the same flow when the attribute is absent. The Console’s embed snippet includes it, and so do the samples on this page.
The script registers the element and sets window.BotShield. This URL serves the latest stable version. To pin a version, see Widget versions. Load it with a plain <script src> tag so the widget can tell which host it came from.

Attributes

site-key is read when the element connects to the page. The other attributes are read when the user clicks, so you can update platform-user-ref after sign-in without re-creating the element.

What the user sees

The button title is always “Verify you’re human”. The status line changes with the state:

Desktop: QR code

On a desktop browser the widget opens a full-page modal with a QR code. The modal names your site by its hostname, without a leading www.: “meridianairlines.com is asking BotShield to confirm you’re human. BotShield never shares your identity — only a signal that you passed.” The user scans the code with their phone camera, the BotShield app opens, and they confirm with their device biometric. The modal closes by itself when the result arrives. The modal footer reads “No personal data is shared with this site”. On an Age Gate the modal says it is verifying age: the title is “Verify your age with BotShield”, and the text reads “meridianairlines.com is asking BotShield to verify your age. BotShield never shares your identity — only an age result.” The modal waits up to about five minutes, matching the life of the verification request. If the user selects Cancel or clicks outside the modal, the widget returns to idle and fires botshield:cancel. On a phone or tablet (iPhone, iPad, iPod or Android user agent) there is no QR code. The widget opens the BotShield app directly with a deep link, then checks for the result every 5 seconds for up to 5 minutes. When the user comes back to your tab, the widget picks up where it left off.

Returning users

Set platform-user-ref for signed-in users and two things change after their first successful verification on your site:
  • Recent Presence. On a gate in Recent Presence mode, a user whose earlier proof is still current passes instantly. No modal opens. The success event has token: null and via: "precheck".
  • Push instead of QR. When a live check is needed and the user has the app with notifications on, BotShield sends the request straight to their phone. The modal title changes to “Check your phone” and reads “Sent to your registered device. Tap the notification to continue — or scan the code if your phone is elsewhere.” The QR code stays visible as a fallback.
Live gates and Age Gates never take the instant path, but they still use push.

Events

All events bubble and cross the Shadow DOM boundary (bubbles: true, composed: true), so you can listen on the element, a parent, or document.
Callbacks and events are equivalent. onsuccess="myFn" calls window.myFn(detail) right after the botshield:success event fires. Use callbacks for plain HTML pages, and events in frameworks and modules where your functions are not global.

botshield:success

Fired when the result is Verified. The detail has one of two shapes, depending on how the verification ran: Handle both shapes:
  • If token is a JWT, send it to your server with request_id and verify it within 120 seconds.
  • If token is null, send request_id and have your server read GET /verification/status right away. A fast-path record reads as pass for 60 seconds, then as expired. A live check reads as completed.
  • If verify-token rejects a token, treat the user as unverified and fall back to the status check by request ID.

botshield:census-status

Fired once per click, as soon as the pre-check answers and before any modal opens. (census is the API’s name for BotShield Gate.)
Most integrations do not need this event. Use botshield:success and botshield:failure for decisions.

botshield:failure

Fired when the result is Unavailable. detail.reason is always present. For the configuration errors, the widget also writes a plain-English explanation to the browser console. For gate_not_found, check the scope value and that the gate is placed in the environment your site key belongs to. For gate_not_active, activate the gate in the Console.

botshield:cancel

Fired when the person closes the desktop modal before the verification finishes, with the Cancel button or a click on the backdrop. detail is { request_id }. The widget returns to idle, and the next click starts a new verification.
It does not fire on success, failure or expiry; those close the modal and fire their own events. The mobile deep-link flow has no modal, so it never fires this event. There is no callback attribute for it: use addEventListener.

botshield:checkout

Fired when the user clicks the action button and the widget is verified. detail is { token, state: "verified" }. token is null after a fast-path pass.

botshield:inline-passkey

Fired only with betas="inline-passkey". detail is { status, reason, request_id }. See Inline passkey.

botshield:reset

Fired by reset(), and when the user clicks a widget in the failed state. No detail.
botshield:ready and botshield:expired are not dispatched. An expired request arrives as botshield:failure with reason expired.

Methods

Call reset() after you consume a result if the user can repeat the action on the same page. Tokens are short-lived, so do not hold one for later.

The action button

The widget draws a second button under the verify button. It is labelled “Checkout” unless you set checkout-label, and it stays disabled until the widget is verified. Because it lives inside the widget, a visitor cannot enable it by editing your page. When a verified user clicks it, you get botshield:checkout.
If you have your own submit button, hide the built-in one with checkout="false". The widget then draws only the verify button, and botshield:checkout never fires.

Forms

When a live verification succeeds inside a <form>, the widget writes the token into a hidden input named botshield_token in that form, creating the input if needed. On the fast path there is no token, so nothing is written. Add the request ID yourself to cover both cases:
Your server reads botshield_token and botshield_request_id from the form post and checks them before it creates the account. Enabling a button in the browser is a convenience, not a control.

BotShield.render()

window.BotShield.render(target, options) creates the element for you and returns a handle.
The handle has verify(), getToken(), reset(), destroy() (removes the element) and element. target is a CSS selector or an element; render() throws if it is not found. Limits. render() cannot set scope, platform-user-ref, link-on-verify, checkout-label, betas or mode. Set them on widget.element as shown, or write the element in HTML.

Inline passkey (Beta)

Beta. This option can change or be removed without a major version. Desktop only.
With betas="inline-passkey", the desktop modal first offers a passkey prompt on the same computer, so a user who already has a BotShield passkey available on that device (for example through a synced passkey manager) can confirm with Touch ID, Face ID or Windows Hello without picking up their phone.
What the user sees, in the modal’s right-hand pane:
  1. “Verify with your passkey — Use Touch ID, Face ID or Windows Hello on this device — no phone needed.” with a Use passkey button and a Scan with your phone instead link.
  2. The browser’s passkey sheet. On success the pane reads “You’re verified” and the modal closes.
  3. If the device cannot do it, or the user prefers their phone, the pane switches to the QR code. When the device is capable, the QR view keeps a Use a passkey on this device instead link.
The flow never dead-ends: every failure falls back to the QR code. Success arrives as the normal botshield:success with via: "ceremony". A new BotShield user enrolls first — a passkey at app.botshield.ai (public beta) or in the BotShield app — and then the inline path is available to them. botshield:inline-passkey reports progress: On an Age Gate the widget skips the inline passkey by itself, because the age signal is read on the user’s phone. The modal opens straight on the QR code, with no passkey pane and no link back to one, and the widget dispatches botshield:inline-passkey with { status: "fallback", reason: "age_gate", request_id }. You can leave betas="inline-passkey" on a page that hosts both gate types.

Theming

Set theme to match your page, or leave auto. The widget fills the width of its container; give it a container around 320 px wide for the intended proportions. Style the exposed parts from your own CSS:
The QR modal has a fixed dark design and is not themeable.

Frameworks

Render the element with site-key and scope already set, and attach listeners with addEventListener. Load sdk.js once, in your HTML shell.
Do not render the element with an empty site-key and fill it in later. Wait until you have the key, then render.
On a single-page app, call reset() or re-create the element when the user starts a new action. A verified widget stays verified until then.

Content Security Policy and origins

Add your page’s origin to the site key’s Allowed Origins in the Console. A live site key must list at least one origin; a test key can have an empty list, which accepts any origin. A mismatch shows up as botshield:failure with reason origin_not_allowed. If your site sends a Content Security Policy, allow: The QR image is drawn by a third-party QR service at api.qrserver.com. The widget sends it the verification link for the current request, which contains the request ID and no personal data. For betas="inline-passkey", your Permissions-Policy header must not disable publickey-credentials-get for https://cdn.botshield.ai.

Next steps

Verify on your server

Check the token or request ID before you trust it.

Testing

Try every path with development keys.

Age Gate

Read the age result for an Age Gate.

Result states

Verified and Unavailable, and how to design for both.