Skip to main content

Webhook Verification

Every webhook from ShieldLabs includes an Assing field — an HMAC-SHA256 signature over the Data object.

How the signature is computed

Assing = HMAC-SHA256( JSON.stringify(Data), secret_key )
Where:
  • Data is the webhook Data object
  • secret_key is your domain’s secret key

Verification implementations

package shield

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         interface{} `json:"Details"`
    LastRequestTime string      `json:"LastRequestTime"`
}

func VerifyWebhook(data WebhookData, assing, secret string) bool {
    b, err := json.Marshal(data)
    if err != nil {
        return false
    }
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(b)
    expected := hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(assing), []byte(expected))
}

Important notes

  • The field name is Assing (not Assign or signature)
  • Use a timing-safe comparison to prevent timing attacks
  • The HMAC is computed over the JSON serialization of Data — not the entire webhook body
  • JSON serialization must be consistent (same key order) — use the structs/types defined in your language

Express.js middleware example

import express from 'express';
import crypto from 'crypto';

export function shieldWebhookMiddleware(secret) {
  return (req, res, next) => {
    const { Data, Assing } = req.body;
    if (!Data || !Assing) {
      return res.status(400).json({ error: 'Missing Data or Assing' });
    }
    const body = JSON.stringify(Data);
    const expected = crypto.createHmac('sha256', secret).update(body).digest('hex');
    try {
      const valid = crypto.timingSafeEqual(
        Buffer.from(Assing, 'hex'),
        Buffer.from(expected, 'hex')
      );
      if (!valid) return res.status(401).json({ error: 'Invalid signature' });
    } catch {
      return res.status(401).json({ error: 'Invalid signature' });
    }
    req.shieldData = Data;
    next();
  };
}

// Usage
app.post('/webhook', express.json(), shieldWebhookMiddleware(process.env.SHIELD_SECRET), (req, res) => {
  const { Score, UserHID } = req.shieldData;
  // ... handle score
  res.status(200).end();
});