Shield
Add invisible bot protection to any web form — embed the widget, then verify the signed token on your server before processing the submission.
Shield protects web forms from bots without requiring any user interaction. It runs a proof-of-work challenge and behavioural analysis in the background, then issues a signed token your server verifies before processing the submission. You'll need two keys from your Shield site settings: a public site key for the widget, and a private secret key for server-side verification.
Step 1 — Embed the widget
Add the widget script to your <head>:
<script src="https://app.verifence.io/shield/widget.js" async></script> Drop a .shield-widget element into your form. The script auto-renders every .shield-widget on the page and injects a hidden field named shield-token, which it populates with a signed token before the form is submitted — you don't add the token field yourself:
<form method="POST" action="/contact">
<input type="text" name="name" placeholder="Name" />
<input type="email" name="email" placeholder="Email" />
<textarea name="message" placeholder="Message"></textarea>
<!-- Renders invisibly and injects a hidden "shield-token" field -->
<div class="shield-widget" data-sitekey="your_site_key"></div>
<button type="submit">Send</button>
</form> Shield.init() call is needed — the widget auto-renders on load. If you render forms dynamically, call Shield.render('.shield-widget') after inserting them, or Shield.execute('your_site_key') to fetch a token programmatically.
Step 2 — Verify on your server
Call the siteverify endpoint after receiving a submission, before doing any processing. It authenticates with your secret key in the body — not the API-KEY header — and does not consume scan credits.
POST https://app.verifence.io/api/shield/siteverify
Content-Type: application/json Request fields
| Field | Required | Description |
|---|---|---|
secret | Yes | Your Shield site's secret key |
token | Yes | The value of the injected shield-token field |
email | No | Email from the form — runs your email rules |
text | No | Free-text field — returns a spam score if spam detection is enabled |
honeypot | No | Value of an optional honeypot field you add — a non-empty value is treated as a bot |
Response fields
| Field | Type | Description |
|---|---|---|
success | boolean | true if the submission passed all checks — the hard signal |
score | integer | Risk score 0–100 (higher = more likely human); informational |
spam_score | float | 0–1 spam probability (only when text is passed and spam detection is on) |
language | string | Detected language of the text (e.g. en) |
geo | object | Country and ASN of the submitter's IP, when available |
degraded | boolean | true if Shield was in fallback mode — treat as a pass |
error | string | Present when success is false — the reason |
cURL
curl -X POST https://app.verifence.io/api/shield/siteverify \
-H "Content-Type: application/json" \
-d '{
"secret": "your_secret_key",
"token": "<token from form>",
"email": "user@example.com",
"text": "Hello, I am interested in your services."
}' {
"success": true,
"score": 91,
"spam_score": 0.03,
"language": "en",
"geo": { "country": "US", "asn": "AS15169" }
} {
"success": false,
"error": "Verification failed"
} PHP
function verifyShieldToken(string $token, string $email = null, string $text = null): array
{
$payload = [
'secret' => getenv('VERIFENCE_SHIELD_SECRET'),
'token' => $token,
];
if ($email) $payload['email'] = $email;
if ($text) $payload['text'] = $text;
$ch = curl_init('https://app.verifence.io/api/shield/siteverify');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_TIMEOUT => 5,
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true) ?? [];
}
// Usage — contact form handler
$result = verifyShieldToken(
token: $request->input('shield-token', ''),
email: $request->input('email'),
text: $request->input('message'),
);
if (!($result['success'] ?? false)) {
return back()->withErrors(['form' => 'Verification failed. Please try again.']);
}
if (isset($result['spam_score']) && $result['spam_score'] > 0.85) {
return back()->withErrors(['message' => 'Your message was flagged as spam.']);
}
sendContactEmail($request->all()); Node.js
async function verifyShieldToken({ token, email, text } = {}) {
const payload = { secret: process.env.VERIFENCE_SHIELD_SECRET, token };
if (email) payload.email = email;
if (text) payload.text = text;
const res = await fetch('https://app.verifence.io/api/shield/siteverify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
signal: AbortSignal.timeout(5000),
});
if (!res.ok) {
throw new Error(`Shield siteverify error: ${res.status}`);
}
return res.json();
}
// Usage — Express contact form route
app.post('/contact', async (req, res) => {
const result = await verifyShieldToken({
token: req.body['shield-token'],
email: req.body.email,
text: req.body.message,
});
if (!result.success) {
return res.status(422).json({ error: 'Verification failed. Please try again.' });
}
if (result.spam_score > 0.85) {
return res.status(422).json({ error: 'Your message was flagged as spam.' });
}
await sendContactEmail(req.body);
res.json({ success: true });
}); Python
import os
import requests
def verify_shield_token(token: str, email: str = None, text: str = None) -> dict:
payload = {"secret": os.environ["VERIFENCE_SHIELD_SECRET"], "token": token}
if email:
payload["email"] = email
if text:
payload["text"] = text
response = requests.post(
"https://app.verifence.io/api/shield/siteverify",
json=payload,
timeout=5,
)
response.raise_for_status()
return response.json()
# Usage — Django or Flask contact form view
result = verify_shield_token(
token=request.POST.get("shield-token", ""),
email=request.POST.get("email", ""),
text=request.POST.get("message", ""),
)
if not result.get("success"):
return error_response("Verification failed. Please try again.")
if result.get("spam_score", 0) > 0.85:
return error_response("Your message was flagged as spam.")
send_contact_email(request.POST) Tips
- Always check
successfirst. It is the hard signal — reject the submission whenever it isfalse. - A missing or empty token fails open. If the widget didn't load (ad blocker, slow connection) the token is empty, and siteverify returns
{ "success": true, "degraded": true }rather than blocking. To fail closed instead, treat an empty token as a rejection before calling siteverify. - Don't block on score alone.
scoreis informational;success: falseis the decision. Usespam_scoreas an extra filter when spam detection is enabled. - Keep the secret key server-side. Never expose it in frontend code.