Webhooks
Webhooks are the primary way to receive Trust Scores on your server. ShieldLabs sends a POST request to your endpoint within ~1 second of the browser completing its fingerprint check.
When WebRTC completes successfully, a second webhook may arrive with "Phase": "update" and a recalculated score.
1. Register your endpoint
Recommended — Dashboard or JWT API:
curl -X PUT "https://account.shieldlabs.ai/api/domains/{domain_id}/webhook" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"callback": "https://your-server.com/shieldlabs/webhook"}'
Alternative — Core Management API:
curl -X POST "https://api.shieldlabs.ai/{domain}:{secret}/callback" \
-H "Content-Type: text/plain" \
-d "https://your-server.com/shieldlabs/webhook"
| Parameter | Description |
|---|
{domain} | Your registered domain hostname, e.g. example.com |
{secret} | Your secret key (32 hex chars) |
2. Webhook payload
ShieldLabs POSTs a JSON body to your endpoint:
{
"Data": {
"RequestID": "550e8400-e29b-41d4-a716-446655440000",
"SessionID": "7a1b2c3d-e89f-4a1b-9c2d-3e4f5a6b7c8d",
"CookieID": "3f2e1d0c-b9a8-7f6e-5d4c-3b2a1f0e9d8c",
"DeviceID": "6ba7b810-9dad-11d1-80b4-00c04fd430c9",
"VisitorID": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"UserHID": "sha256_of_user_id_or_anonymous",
"IP": "93.184.216.34",
"OS": "Windows",
"Country": "US",
"Score": 42,
"Details": [
{ "Value": 10, "Description": "Is datacenter" },
{ "Value": 10, "Description": "Browser timezone ≠ IP-timezone" }
],
"LastRequestTime": "2026-04-14T10:00:00Z",
"Phase": "initial"
},
"Assing": "3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e"
}
Fields
| Field | Type | Description |
|---|
Data.RequestID | string (UUID) | Unique check identifier — correlate with browser callback |
Data.SessionID | string (UUID) | Browser session (sessionStorage, ~10 min TTL) |
Data.CookieID | string (UUID) | Persistent visitor cookie ID |
Data.DeviceID | string (UUID) | Derived from stable MixVisit fingerprint components |
Data.VisitorID | string (UUID) | Derived from DeviceID + CookieID pair |
Data.UserHID | string | Hashed user ID from checkAuthenticatedUser(), or "anonymous" |
Data.IP | string | Client IP address |
Data.OS | string | Detected operating system (from User-Agent) |
Data.Country | string | 2-letter ISO country code from IP geolocation |
Data.Score | integer | Trust Score (0–999). Higher = more suspicious. |
Data.Details | array | Scored signals — each has Value (points) and Description (signal name) |
Data.LastRequestTime | string (ISO 8601) | Timestamp of the browser check |
Data.Phase | string | "initial" (first score) or "update" (after WebRTC recalculation) |
Assing | string (hex) | HMAC-SHA256 signature of Data |
The field is spelled Assing (not Assign). This is the canonical field name — use it exactly when verifying.
3. Verify the signature
Always verify Assing before processing the payload. The signature is HMAC-SHA256 over the JSON serialization of the entire Data object (including Phase when present), keyed with your secret key.
Shield.Core uses Go’s json.Marshal — field order follows struct definition. Include all fields present in the payload when verifying.
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
)
type WebhookData struct {
RequestID string `json:"RequestID"`
SessionID string `json:"SessionID"`
CookieID string `json:"CookieID"`
DeviceID string `json:"DeviceID"`
VisitorID string `json:"VisitorID"`
UserHID string `json:"UserHID"`
IP string `json:"IP"`
OS string `json:"OS"`
Country string `json:"Country"`
Score int `json:"Score"`
Details []ScoreDetail `json:"Details"`
LastRequestTime time.Time `json:"LastRequestTime"`
Phase string `json:"Phase,omitempty"`
}
func verifyWebhook(data WebhookData, assing, secret string) bool {
b, _ := json.Marshal(data)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(b)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(assing), []byte(expected))
}
See Webhook Verification for more language examples.
4. Respond to the webhook
Your endpoint must:
- Accept
POST requests with Content-Type: application/json
- Return any
2xx status code within 1 second
- Use HTTPS
If ShieldLabs receives a non-2xx response or a timeout, it does not retry.
5. Handle the score
// Express.js
app.post('/shieldlabs/webhook', express.json(), (req, res) => {
const { Data, Assing } = req.body;
if (!verifyWebhook(Data, Assing, process.env.SHIELD_SECRET)) {
return res.status(401).end();
}
res.status(200).end(); // respond immediately
// process async — use Phase to handle initial vs update
processShieldScore(Data).catch(console.error);
});
async function processShieldScore(data) {
const { Score, UserHID, RequestID, Details, Phase } = data;
await db.sessions.upsert({
requestId: RequestID,
userHid: UserHID,
score: Score,
signals: Details,
phase: Phase,
});
if (Score >= 100) {
await blockUser(UserHID);
} else if (Score >= 40) {
await flagForReview(UserHID, Score);
}
}
Score thresholds
| Score | Risk | Recommended action |
|---|
| 0–9 | Clean | Allow |
| 10–29 | Low | Allow / monitor |
| 30–59 | Medium | Monitor / log |
| 60–99 | High | Require 2FA or CAPTCHA |
| 100+ | Bot | Block |
| 999 | Banned | Rate limit exceeded — 1h auto-ban |