Skip to content
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:

  1. Read the raw request body exactly as received. Do not parse and re-serialize it, or the bytes (and the signature) will differ.
  2. Recompute HMAC-SHA256(secret, "{timestamp}.{rawBody}") and hex-encode it.
  3. Compare it to the header value using a constant-time comparison.
  4. Reject the request if X-Dango-Timestamp is 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