Verify a Webhook Signature
Verify a Webhook Signature
When you pass a webhookUrl to Submit, Dango sends a single signed
POST once every line in the batch is enriched. The body is minimal:
{
"project_id": "aB3xK9",
"event": "enrichment.completed",
"status": "completed",
"counts": { "total": 100, "enriched": 100, "failed": 0 }
}The webhook is only a notification: call Export a CSV to fetch the enriched result.
Signature scheme
Every delivery carries two headers:
X-Dango-Signature: sha256=<hex>: the HMAC-SHA256 of your organization’s webhook secret over the string"{timestamp}.{body}", hex-encoded.X-Dango-Timestamp: <unix-seconds>: the same timestamp, for anti-replay.
To verify an inbound request:
- Read the raw request body exactly as received. Do not parse and re-serialize it, or the bytes (and the signature) will differ.
- Recompute
HMAC-SHA256(secret, "{timestamp}.{rawBody}")and hex-encode it. - Compare it to the header value using a constant-time comparison.
- Reject the request if
X-Dango-Timestampis too old (here, 5 minutes) to prevent replay.
The secret is per-organization; keep it in an environment variable
(DANGO_WEBHOOK_SECRET below), never in source.
Examples
#!/usr/bin/env bash
# Reads the raw body on stdin. Args: timestamp, signature header value.
set -euo pipefail
RAW_BODY="$(cat)"
TIMESTAMP="$1"
SIGNATURE="${2#sha256=}" # strip the "sha256=" prefix
# Anti-replay: reject if older than 5 minutes.
NOW=$(date +%s)
if (( NOW - TIMESTAMP > 300 )); then
echo "stale timestamp" >&2
exit 1
fi
EXPECTED=$(printf '%s.%s' "$TIMESTAMP" "$RAW_BODY" \
| openssl dgst -sha256 -hmac "$DANGO_WEBHOOK_SECRET" -r \
| cut -d' ' -f1)
# Constant-time-ish compare via HMAC of both sides.
if [[ "$EXPECTED" == "$SIGNATURE" ]]; then
echo "valid"
else
echo "invalid" >&2
exit 1
fi