None of these tells you who the person is.
In the page, live
When the widget offers Link with BotShield ID, it also keeps a live connection to BotShield that answers one question: is this account linked right now? It needs no tap.- The offer is on in the Console, on the site key’s Link with BotShield ID surface or on the gate named in
scope. platform-user-refis set.site-keyis apk_test_...orpk_live_...key, and the page origin is in its allowed origins.
botshield:presence
The widget fires botshield:presence with its first answer, and again each time the answer changes.
fresh and stale both mean linked. fresh_until is a time BotShield works out from the person’s latest passkey use. It is null when state is none. BotShield does not send a change from fresh to stale that comes only from the clock passing fresh_until. If you need that, compare fresh_until with your own clock.
The detail never carries a handle, a BotShield ID, a gate name or a request ID.
The widget acts on the answer itself: on none it puts the Link with BotShield ID chip back, and on fresh or stale it shows Account secured. Listen for the event when the rest of your page has to change too.
When the connection drops
The widget never shows an account as linked without an answer from BotShield.
The re-read when the tab becomes visible again needs widget
3.0.12 or later. The current stable version at /sdk.js is 3.0.15. Pinned 3.0.9 has the live connection and the 60-second fallback only. See Widget versions.On your server, pushed
Two webhook events keep your records in step when the person is not on your site. Both carry the account’shandle, an opaque value that starts with OP_, and never your platform-user-ref.
surface_id names the Link with BotShield ID surface the account was linked on. It is null for a link made through a gate. See Token and webhooks for the full payloads.
environment field, and a person’s handle is the same in Development and Production. Keep Development traffic away from the endpoint your production system reads. Set up delivery, signatures and deduplication in the Webhooks overview.
On a fresh pass
Every time the person passes, with the chip or at one of your gates, your page receives a signed token. Your server checks it withPOST /sdk/verify-token, and the claims say whether the account is linked at that moment. At a gate, send the same platform-user-ref with the request, or the claim has no account to look up and reads false.
data.data.claims.trusted. It is true only when an active link exists for your platform and the platform-user-ref that came with the pass. It is false for an account that was never linked, or was unlinked. This is the strongest of the three answers: it is signed, and it comes from a live human who just used their passkey. See Token and webhooks for every claim and check.
Ask from your server
Your server can read and end links with its API key, with no tap:GET /secured-accounts/status(securedAccounts.getStatus):{ state, fresh_until }for one of your user IDs, the same answer the widget’s live connection gives.GET /secured-accounts/list(securedAccounts.list): your registry, by handle.POST /secured-accounts/revoke(securedAccounts.revoke): unlink one account, withreason: "platform"or"credential_changed".
Putting it together
1
On account.secured
Store the handle beside your user, and mark the account linked.
2
On every pass
Verify the token on your server. Update your record from
trusted.3
On account.unlinked
Find the user by handle and mark the account not linked.
4
When a sign-in credential changes
Call
securedAccounts.revoke with reason: credential_changed. The person re-links in one tap behind your own re-auth.5
If you missed an event
Call
securedAccounts.getStatus or securedAccounts.list to reconcile.6
In the page
Listen for
botshield:presence and update what the person sees while they are on your site.