Skip to main content
When you set a secret on a webhook subscription, Struct signs every delivery with an HMAC so you can confirm the request is genuine and the body was not tampered with in transit.
Always verify signatures in production. Your endpoint is a public URL — without verification, anyone who discovers it can POST forged events to it.

The signature header

Each delivery includes:
X-Webhook-Signature: sha256=<hex>
  • Algorithm: HMAC-SHA256.
  • Key: the secret you set when creating (or rotating) the webhook.
  • Message: the raw request body bytes, exactly as received — do not re-serialize the JSON before computing the HMAC.
  • Encoding: lowercase hex, prefixed with sha256=.
There is no timestamp component, so you verify the body alone.

How to verify

Compute the HMAC-SHA256 of the raw body with your secret, prefix it with sha256=, and compare it to the header using a constant-time comparison.
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.WEBHOOK_SECRET;

app.post(
  "/webhooks",
  express.raw({ type: "application/json" }), // keep the raw body
  (req, res) => {
    const received = req.get("X-Webhook-Signature") || "";
    const expected =
      "sha256=" +
      crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");

    const a = Buffer.from(received);
    const b = Buffer.from(expected);
    if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
      return res.status(401).send("invalid signature");
    }

    const event = JSON.parse(req.body.toString("utf8"));
    res.sendStatus(200); // ack fast, then process
    handleEvent(event);
  },
);
Frameworks that auto-parse JSON often discard the raw body. Make sure you hash the bytes as received — re-serializing the parsed object can reorder keys or change whitespace and break the signature.

Other delivery headers

Every delivery also carries these headers (handy for logging and routing):
HeaderDescription
X-Webhook-IDUUID of the webhook subscription that produced the delivery.
X-Delivery-IDUUID of this specific delivery. Matches the id field in the body — use it to deduplicate.
X-Event-TypeThe event name, e.g. trader_whale_trade.
X-AttemptDelivery attempt number, starting at 1. Struct retries failed deliveries — see the attempt field.
X-Webhook-SignatureThe HMAC signature described above (only present when a secret is set).

Rotating your secret

If a secret may have leaked, rotate it. New deliveries are signed with the new secret immediately:
curl -X POST https://api.struct.to/v1/webhooks/{webhook_id}/rotate-secret \
  -H "X-API-Key: YOUR_API_KEY"
Last modified on June 3, 2026