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

FieldTypeDescription
Data.RequestIDstring (UUID)Unique check identifier — correlate with browser callback
Data.SessionIDstring (UUID)Browser session (sessionStorage, ~10 min TTL)
Data.CookieIDstring (UUID)Persistent visitor cookie ID
Data.DeviceIDstring (UUID)Derived from stable MixVisit fingerprint components
Data.VisitorIDstring (UUID)Derived from DeviceID + CookieID pair
Data.UserHIDstringHashed user ID from 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
Data.Phasestring"initial" (first score) or "update" (after WebRTC recalculation)
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 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

ScoreRiskRecommended action
0–9CleanAllow
10–29LowAllow / monitor
30–59MediumMonitor / log
60–99HighRequire 2FA or CAPTCHA
100+BotBlock
999BannedRate limit exceeded — 1h auto-ban