---
title: "Shield Server-Side Validation"
description: "Validate Shield tokens with the siteverify API — Turnstile-compatible responses, error codes, and how to use the 1–100 humanity score."
url: https://edge.network/docs/shield/siteverify/
---

# Shield Server-Side Validation

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.

## 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](https://edge.network/docs/shield/verified-agents) 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](https://edge.network/docs/shield/verified-agents) 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 — low, because it's
automation — and the `agent`
field is how you 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`** —
nothing is ever blocked — and 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](https://edge.network/docs/shield/analytics).
When the numbers look right, switch shadow mode off — no code changes needed, since a
correct integration already checks `success`.

Shadow mode is the risk-free way to trial Shield on production traffic: real
visitors can't be inconvenienced, and you get a fully quantified preview of what
enforcement would have done.

## 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](https://edge.network/docs/shield/testing).

## 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 API
```

The 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 — human motor noise raises the score, machine-perfect input lowers it —
not just the load-time environment.

Token lifetime is **5 minutes** and each token is valid for exactly one
siteverify call. Validate at the moment of form processing, not ahead of time.

## Next Steps

[Offline Verification Verify tokens locally via JWKS — zero network calls](https://edge.network/docs/shield/offline-verification) [Migrating from Turnstile Same response shape — swap and go](https://edge.network/docs/shield/turnstile-migration)
[Back to Docs](https://edge.network/docs) [Need help?](https://edge.network/support)
