Shield
Server-side validation
Every token must be validated from your server with the siteverify endpoint. The response shape is compatible with Cloudflare Turnstile, plus a humanity score.
On this page8 sections
The endpoint#
POST https://shield.edge.network/siteverify
Content-Type: application/x-www-form-urlencoded
secret=es_secret_...&response=<token>Both application/x-www-form-urlencoded and application/json bodies are accepted.
| Parameter | Required | Description |
|---|---|---|
secret |
Yes | Your widget’s secret key (es_secret_…) |
response |
Yes | The token from the edge-shield-response form input |
remoteip |
No | The submitting visitor’s IP address. Recommended: tokens are bound to the IP they were issued to, so passing this rejects tokens farmed on one machine and spent from another |
Responses#
Success:
{
"success": true,
"score": 92,
"challenge_ts": "2026-07-21T08:00:00.000Z",
"hostname": "example.com",
"error-codes": []
}Failure:
{
"success": false,
"error-codes": ["timeout-or-duplicate"]
}| Field | Description |
|---|---|
success |
Whether the token is genuine, unexpired, unused, and matches your widget |
score |
Humanity score, 1–100. Not present on failures. (Shield extension, not in Turnstile) |
challenge_ts |
ISO 8601 timestamp of when the token was issued |
hostname |
The hostname the widget ran on |
agent |
Present when the token belongs to a verified agent that declared its identity. (Shield extension, not in Turnstile) |
shadow |
Present only for widgets in shadow (report-only) mode. See below. (Shield extension, not in Turnstile) |
error-codes |
Machine-readable failure reasons (see below) |
Error codes#
| Code | Meaning |
|---|---|
missing-input-secret |
No secret was supplied |
invalid-input-secret |
The secret doesn’t match any active widget |
missing-input-response |
No token was supplied |
invalid-input-response |
The token is malformed, expired, from a different widget, or from a disallowed hostname |
timeout-or-duplicate |
The token was already used. Tokens are strictly single-use |
agent-blocked |
A verified agent was rejected because the widget’s agent policy is set to Block |
Verified agent tokens#
When automation cryptographically declares its identity (via Web Bot Auth or a confirmed crawler check) and your widget’s agent policy allows it, the token carries the verified identity. The score stays an honest humanity estimate, which is low because it’s automation. Use the agent field to route:
{
"success": true,
"score": 8,
"agent": { "domain": "agent.bot.goog", "verified": true, "method": "web-bot-auth" },
"challenge_ts": "2026-07-21T08:00:00.000Z",
"hostname": "example.com",
"error-codes": []
}Check agent.verified before trusting a low score: a verified agent is declared automation you may want to serve, while a low score with no agent field is automation that refused to identify itself.
Shadow (report-only) mode#
A widget can be switched to shadow mode in the dashboard (Shield → widget → Settings). In shadow mode siteverify never returns success: false, so nothing is ever blocked. Instead, the response carries what strict enforcement would have decided:
{
"success": true,
"shadow": {
"enabled": true,
"enforced": false,
"would_succeed": false,
"error-codes": ["invalid-input-response"]
},
"error-codes": []
}Log shadow.would_succeed and shadow["error-codes"] during the trial; would-fail and would-escalate counts also accumulate in your widget’s analytics. When the numbers look right, switch shadow mode off. No code changes are needed, since a correct integration already checks success.
Testing your integration#
Fixed test secrets (es_secret_test_pass, es_secret_test_fail, es_secret_test_spent) return deterministic responses so your test suite can exercise every branch without real keys. See Testing & CI.
Using the humanity score#
The score expresses confidence: 1 is confirmed automation, 100 is a confirmed human. “Not human” doesn’t have to mean “not welcome”. A legitimate AI agent acting for a customer will score low, and you may prefer to route it to your API rather than block it.
const { success, score } = await siteverify(token)
if (!success) reject() // invalid, expired, or replayed token
else if (score >= 70) allow() // confident human
else if (score >= 40) stepUp() // uncertain: e.g. require email confirmation
else routeOrBlock() // automation: block, or redirect to your APIThe thresholds are yours to tune. As reference points: in managed mode, background attempts scoring below 40 are automatically escalated to an interactive confirmation before a token is issued, so tokens arriving at your server from managed widgets already had a floor applied.
The score you receive is also the freshest available: the widget verifies at page load, then re-mints its token with a behaviour-informed score once the visitor has actually typed or moved the pointer. By the time the form is submitted, the token usually reflects real interaction as well as the load-time environment. Human motor noise raises the score; machine-perfect input lowers it.
Next steps
Something unclear or out of date? Tell us