---
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 |

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

## 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.

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

[Migrating from Turnstile Same response shape — swap and go](https://edge.network/docs/shield/turnstile-migration) [Analytics Verification metrics and score trends](https://edge.network/docs/shield/analytics)
[Back to Docs](https://edge.network/docs) [Need help?](https://edge.network/support)
