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.
1. Register your endpoint
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, e.g. example.com |
{secret} | Your secret key from the dashboard |
To update the webhook URL, call the same endpoint with the new URL.
2. Webhook payload
ShieldLabs POSTs a JSON body to your endpoint:
{
"Data": {
"RequestID": "550e8400-e29b-41d4-a716-446655440000",
"DeviceID": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"VisitorID": "7f3e9a12-4b5c-4d6e-8f9a-0b1c2d3e4f5a",
"UserHID": "sha256_of_user_id_or_anonymous",
"IP": "93.184.216.34",
"OS": "Windows",
"Country": "US",
"Score": 42,
"Details": [
{ "Value": 20, "Description": "Is datacenter" },
{ "Value": 10, "Description": "Browser timezone ≠ IP-timezone" },
{ "Value": 12, "Description": "..." }
],
"LastRequestTime": "2026-04-14T10:00:00Z"
},
"Assing": "3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e"
}
Fields
| Field | Type | Description |
|---|
Data.RequestID | string (UUID) | Unique session identifier |
Data.DeviceID | string (hex) | MurmurHash3 of browser fingerprint |
Data.VisitorID | string (UUID) | Persistent visitor identifier (localStorage) |
Data.UserHID | string | Hashed user ID passed to 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 |
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 Data, keyed with your secret key.
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
)
type WebhookData struct {
RequestID string `json:"RequestID"`
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 any `json:"Details"`
LastRequestTime string `json:"LastRequestTime"`
}
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))
}
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
processShieldScore(Data).catch(console.error);
});
async function processShieldScore(data) {
const { Score, UserHID, RequestID, Details } = data;
await db.sessions.upsert({
requestId: RequestID,
userHid: UserHID,
score: Score,
signals: Details,
});
if (Score >= 100) {
await blockUser(UserHID);
} else if (Score >= 40) {
await flagForReview(UserHID, Score);
}
}
Score thresholds
| Score | Risk | Recommended action |
|---|
| 0–15 | Low | Allow |
| 16–39 | Medium | Monitor / log |
| 40–69 | Elevated | Require 2FA or CAPTCHA |
| 70–99 | High | Block or manual review |
| 100+ | Bot | Block |
| 999 | Banned | Rate limit exceeded — 1h auto-ban |