Skip to content
dhanu docs
Browse pages

Webhooks

Verifying signatures

Check Dhanu-Signature before trusting a webhook, with the SDK helper or a few lines of your own.

Every delivery is signed with your endpoint’s secret. Verify it on the raw request body, before parsing, or an attacker who knows your URL can feed your handler anything.

With the SDK

import { Dhanu, WebhookSignatureError } from "dhanu";

const dhanu = new Dhanu(process.env.DHANU_API_KEY!);

app.post("/hooks/dhanu", express.raw({ type: "application/json" }), async (req, res) => {
  try {
    const event = await dhanu.webhooks.verify(
      req.body.toString("utf8"),
      req.header("Dhanu-Signature"),
      process.env.DHANU_WEBHOOK_SECRET!,
    );
    res.sendStatus(200);
    queue.add(event);
  } catch (err) {
    if (err instanceof WebhookSignatureError) return res.sendStatus(400);
    throw err;
  }
});
import os
from dhanu import Dhanu, WebhookSignatureError

dhanu = Dhanu(os.environ["DHANU_API_KEY"])

@app.post("/hooks/dhanu")
def dhanu_hook():
    try:
        event = dhanu.webhooks.verify(
            request.get_data(),
            request.headers.get("Dhanu-Signature"),
            os.environ["DHANU_WEBHOOK_SECRET"],
        )
    except WebhookSignatureError:
        return "", 400
    queue.put(event)
    return "", 200

verify returns the parsed envelope and throws when the header is missing, the HMAC does not match, or the timestamp is more than five minutes old.

By hand

  1. Read Dhanu-Signature: t=<timestamp>,v1=<hex>[,v1=<hex>].
  2. Reject if t is more than 300 seconds from now.
  3. Compute HMAC-SHA256(secret, "<t>." + rawBody) as lowercase hex.
  4. Compare it, in constant time, against each v1 value, during a secret rotation there are two, and either match is valid.