<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.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 leadingwww.: “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.
Mobile: deep link
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
Setplatform-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: nullandvia: "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.
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.
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
tokenis a JWT, send it to your server withrequest_idand verify it within 120 seconds. - If
tokenisnull, sendrequest_idand have your server readGET /verification/statusright away. A fast-path record reads aspassfor 60 seconds, then asexpired. A live check reads ascompleted. - If
verify-tokenrejects 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.
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
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 setcheckout-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.
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:
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.
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.
- “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.
- The browser’s passkey sheet. On success the pane reads “You’re verified” and the modal closes.
- 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.
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
Settheme 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:
Frameworks
Render the element withsite-key and scope already set, and attach listeners with addEventListener. Load sdk.js once, in your HTML shell.
- React
- Vue
site-key and fill it in later. Wait until you have the key, then render.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 asbotshield: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.
