- Pattern A: verify the widget’s result. The web component runs the verification and your server checks the token or request ID it produced.
- Pattern B: server-to-server. Your server creates the verification request, you show the link in your own UI, and you learn the result by webhook or polling.
https://api.botshield.ai/operations. Every operation answers HTTP 200 with a data envelope, and handler errors arrive inside it as data.error, also with HTTP 200. Check data.error before you read the payload. See API overview and Errors.
TypeScript SDK. Pass
serverURL when you construct the client; the package does not default to production. apiKeyAuth is sent as the Authorization header exactly as you give it, so include the Bearer prefix. The SDK does not unwrap the envelope, and it uses camelCase field names. See TypeScript SDK.Pattern A: verify the widget’s result
The widget’sbotshield:success event gives your page a request_id and a token. The token is a signed JWT or null; it is never a request ID. Send both to your server with the action they protect.
The attestation token
The token describes the event, never the person. It has no subject, no user ID and no email. It also has no gate claim: see What to check.
Verify the token
- With the API
- Locally with the JWKS
POST /sdk/verify-token needs no API key. The token is the proof.census is the API’s name for BotShield Gate.)A valid token:200, with valid: false:reason is "Invalid token — signature verification failed" and there are no claims.When there is no token
On a gate in Recent Presence mode, a returning human can pass without a phone step. The widget’s success event then hastoken: null, via: "precheck" and a request_id. Confirm it from your server with GET /verification/status, which needs no API key:
What to check
verify-token needs no credentials and is not bound to an audience. It answers valid: true for a genuine token issued for any organization, and it does not consume the token, which stays valid for its full 120 seconds. The same is true of a local signature check. So a valid signature is only the first test. Before you let the action through, confirm all of these on your server:
- The token verifies (signature, issuer, not expired), or the status is
completedorpass. organization_idis yours. Copy your Organization ID from Settings → Developer Tools → API Keys in the Console and keep it in your configuration. Acreate-sessionresponse carries the same value asorganization.id.- The
request_idbelongs to this action. In Pattern B, compare it with the request you created. In both patterns, record everyrequest_idyou accept and reject repeats, so one verification cannot be replayed for many actions. - The gate is the one you expect. The token carries no gate claim, so if you run several gates, or both environments, read
GET /verification/statusand comparescopewith your gate key andmetadata.environmentwithproduction.
Pattern B: server-to-server
Use this when you draw your own UI, for a native app, a kiosk, or a back-office flow. Every request runs the live check on the user’s phone; the Recent Presence fast path is a widget feature. Server-to-server verifications count in the gate’s Overview metrics in the Console, the same as widget verifications.1
Create a session
Call The same three values are also returned under the older names
POST /sdk/create-session with your API key: bs_dev_… for Development gates, bs_production_… for Production gates. BotShield resolves the gate in the environment of the key that calls it, so the same gate key can exist in Development and in Production as two independent gates.session_token, expires_at and expires_in_seconds. The grant token lives 5 minutes and works once: the next step consumes it. It only authorizes creating one verification request; it is not a user session.2
Create the verification request
Call
POST /sdk/create-verification-link with the grant token as the Bearer credential, not your API key.Errors arrive as
data.data.error with HTTP 200:3
Show the link to the user
On desktop, render
web_url as a QR code with your own QR library and ask the user to scan it with their phone camera. On mobile, open web_url or deep_link. The user needs a BotShield ID — web_url opens the BotShield web app (public beta), where a first-time user creates a passkey with no download; deep_link opens the BotShield app if they have it. They confirm with their device biometric and are handed back to what they were doing.4
Learn the result
- Webhook (preferred)
- Polling
Add an endpoint in the Console under Settings → Developer Tools → Webhooks and subscribe to One endpoint receives deliveries from both environments, so branch on the top-level
gate.human_verified and gate.unavailable. Match the delivery to your request by request_id.environment (development or production). metadata holds only the keys you supplied. Verify the signature on every delivery, and treat deliveries as at-least-once. See Webhooks and Webhook events for the full payloads and the gate.unavailable reasons.Status values
verification/status returns its payload directly under data, with no second data level.
The status response never contains identity fields. It reports the request, not the person.
The 409 case
BotShield allows one pending request per user and gate. The user is thepartner_user_ref you send to create-verification-link, or else the partner_user_id you sent to create-session. While that user has a pending, unexpired request for the gate, a second create-verification-link for the same gate returns:
create-session (the grant token you used for the rejected call is still unused, so you can also reuse it within its 5 minutes).
revoke-verification clears exactly the requests the 409 rule matches, the pending, unexpired ones for that user and gate, and returns how many in revoked_count. Pass the same identifier in partner_user_id that the request was created with.
A completed or failed request never blocks a new one, so you can ask the same user to verify at the same gate again as soon as the last request finishes. The rule applies only when the request names a user: leave partner_user_id off create-session and partner_user_ref off create-verification-link if you do not need it. It also does not apply to requests the web component makes with a site key.
Next steps
Webhooks
Add an endpoint and verify deliveries.
TypeScript SDK
Install and configure
botshield-sdk.Age Gate
Read the age result on your server.
Errors
The error envelope and status codes.
