Sign inbound webhook requests
Every inbound POST requires X-Timestamp, X-Nonce, and X-Signature. The timestamp is Unix seconds and must be within plus or minus 300 seconds of server time. The nonce must contain 1 to 128 visible non-whitespace ASCII characters; a UUID is recommended. A nonce is reserved per webhook for 600 seconds, so replaying the same nonce is rejected.
Compute lowercase hex HMAC-SHA256 with the display-once signing secret over the exact bytes:
timestamp + ":" + nonce + ":" + raw_request_body
Do not parse and reserialize JSON between signing and sending. Even harmless whitespace or key-order changes alter the signed bytes.
Curl:
TS=$(date +%s); NONCE=$(uuidgen); BODY='{"event":"example"}'SIG=$(printf '%s:%s:%s' "$TS" "$NONCE" "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -hex | awk '{print $2}')curl -X POST "$WEBHOOK_URL" -H "Content-Type: application/json" -H "X-Timestamp: $TS" -H "X-Nonce: $NONCE" -H "X-Signature: $SIG" -d "$BODY"Node:
const body = JSON.stringify({ event: "example" });const ts = Math.floor(Date.now() / 1000).toString();const nonce = crypto.randomUUID();const signature = crypto.createHmac("sha256", secret).update(`${ts}:${nonce}:${body}`).digest("hex");Python:
body = b'{"event":"example"}'signed = timestamp.encode() + b":" + nonce.encode() + b":" + bodysignature = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()Related actions
Section titled “Related actions”arinova.webhook.create_webhook, arinova.webhook.get_webhook
Also known as
webhook signing, HMAC, X-Signature, X-Timestamp, X-Nonce, sender integration
Build a7f47a5ca54ddcf7806cd48b81ce1b9827042766