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