Skip to main content

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"
ParameterDescription
{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

FieldTypeDescription
Data.RequestIDstring (UUID)Unique session identifier
Data.DeviceIDstring (hex)MurmurHash3 of browser fingerprint
Data.VisitorIDstring (UUID)Persistent visitor identifier (localStorage)
Data.UserHIDstringHashed user ID passed to checkAuthenticatedUser(), or "anonymous"
Data.IPstringClient IP address
Data.OSstringDetected operating system (from User-Agent)
Data.Countrystring2-letter ISO country code from IP geolocation
Data.ScoreintegerTrust Score (0–999). Higher = more suspicious.
Data.DetailsarrayScored signals — each has Value (points) and Description (signal name)
Data.LastRequestTimestring (ISO 8601)Timestamp of the browser check
Assingstring (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

ScoreRiskRecommended action
0–15LowAllow
16–39MediumMonitor / log
40–69ElevatedRequire 2FA or CAPTCHA
70–99HighBlock or manual review
100+BotBlock
999BannedRate limit exceeded — 1h auto-ban