Webhook consumer documentation
Register an endpoint on the subscribe page. The tool sends a POST request to your endpoint for each announcement. You do not poll.
Your secret
The subscribe page shows your secret one time, directly after registration. Copy it and store it in a safe place. The tool does not show it again.
Warning: a person who has the secret can send signed requests to your endpoint. Keep it private.
To replace the secret, open the unsubscribe link that the page shows with the secret, then register again. The tool has no in-place secret rotation.
Payload
Each delivery is a POST request with this JSON body:
{
"event_id": "<announcement_id>.<revision>.<kind>",
"kind": "publish",
"announcement": {
"id": "ann_01J9XK...",
"revision": 1,
"slug": "2026-08-upgrade-v5-1-0",
"type": "upgrade",
"networks": ["mainnet"],
"audiences": ["operators"],
"severity": "critical",
"title": "Upgrade to v5.1.0 required by 2026-08-20 14:00 UTC",
"body_md": "...markdown...",
"actions_required": [
{
"action": "Upgrade node to v5.1.0",
"deadline": "2026-08-20T14:00:00Z",
"applies_to": ["sequencer"]
}
],
"links": [
{ "label": "GitHub release", "url": "https://github.com/AztecProtocol/aztec-packages/releases/tag/v5.1.0" }
],
"published_at": "2026-08-06T10:00:00Z"
}
}The kind field is publish, update or reminder for a real delivery. You can also send a test event with kind: "test". See "Verification test event" below.
Headers
| Header | Value |
|---|---|
x-announce-event-id | Same value as event_id in the body |
x-announce-timestamp | Unix seconds (integer), as a string |
x-announce-signature | v1=<hex> — see Signature verification below |
Signature verification
Compute v1=hex(hmac_sha256(secret, timestamp + "." + body)). Use the raw request body, before JSON parsing. Compare the result with the x-announce-signature header. Reject the request if they are different.
import { createHmac, timingSafeEqual } from 'node:crypto';
const secret = process.env.ANNOUNCE_WEBHOOK_SECRET; // the value shown once at registration
const timestamp = req.headers['x-announce-timestamp'];
const signature = req.headers['x-announce-signature'];
const body = req.rawBody; // raw request body as a string, not the parsed JSON
// Reject a delivery whose timestamp is far from your own clock. The signature
// covers the timestamp, so an attacker cannot change it, but a captured
// delivery stays valid forever without this check.
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
if (!Number.isFinite(age) || age > 300) {
throw new Error('stale timestamp');
}
const expected = 'v1=' + createHmac('sha256', secret)
.update(`${timestamp}.${body}`)
.digest('hex');
const a = Buffer.from(signature ?? '');
const b = Buffer.from(expected);
if (a.length !== b.length || !timingSafeEqual(a, b)) {
throw new Error('invalid signature');
}Use a constant-time comparison for the signature. A character-by-character comparison stops at the first difference it finds. The time it takes can tell an attacker how much of the signature is correct.
timingSafeEqual throws an error when the two buffers have different lengths. Check the length first, before you call it.
The tool signs the timestamp together with the body, so nobody can change it in transit. Your endpoint must still reject a delivery with an old timestamp. Without this check, a captured delivery stays valid forever. A window of five minutes is common. The tool signs each retry with a new timestamp. A five-minute window does not reject a retry. This check is different from event_id deduplication. Deduplication stops a repeat of a delivery you already processed. The timestamp window stops an old delivery being replayed at you later.
Retries
The tool makes up to 5 delivery attempts for each event. After a failed attempt, the tool waits 2, 5, 10, 20 and then 30 minutes before the next attempt. After the fifth failed attempt, the tool marks the delivery as exhausted and stops.
Idempotency
A retry can send the same event more than one time. Use event_id to identify duplicates. The value is the same for each attempt of one announcement, revision and delivery kind.
Verification test event
Registration does not activate the webhook. It gives you the secret and a webhook page. Put the secret in your endpoint's configuration first, then click Send test event on the webhook page. The tool sends one signed request with kind: "test" and an event_id of the form whtest_<subscription_id>. Your endpoint must check the signature the same way as for a real delivery and answer with a 2xx status. You can send the test as often as you need. The webhook is active after the first passed test.