Webhooks
Webhooks let your application receive real-time HTTP notifications when optimization jobs change state, without polling the Sites API.
Overview
Section titled “Overview”Configure a webhook endpoint in your dashboard under Settings → Webhooks. Pageflare will send a POST request to your URL whenever a watched event occurs.
Each delivery includes:
- A JSON body describing the event
- An
X-Pageflare-Signatureheader for verification (see Signature verification) - An
X-Pageflare-Eventheader naming the event type
Event types
Section titled “Event types”| Event | Triggered when |
|---|---|
site.completed | An optimization job reaches complete status |
site.errored | An optimization job reaches errored status |
Payload format
Section titled “Payload format”All webhook payloads share a common envelope:
{ "id": "evt_abc123", "type": "site.completed", "created_at": "2026-03-12T10:30:12.000Z", "data": { ... }}| Field | Type | Description |
|---|---|---|
id | string | Unique event ID |
type | string | Event type (see above) |
created_at | string | ISO 8601 timestamp of when the event was generated |
data | object | Event-specific payload |
site.completed
Section titled “site.completed”{ "id": "evt_abc123", "type": "site.completed", "created_at": "2026-03-12T10:30:12.000Z", "data": { "id": "site_abc123", "status": "complete", "fileCount": 42, "bytesIn": 524288, "bytesOut": 327680, "bytesSaved": 196608, "createdAt": "2026-03-12T10:30:00.000Z", "completedAt": "2026-03-12T10:30:12.000Z" }}site.errored
Section titled “site.errored”{ "id": "evt_abc123", "type": "site.errored", "created_at": "2026-03-12T10:30:05.000Z", "data": { "id": "site_abc123", "status": "errored", "error": "Invalid zip archive: central directory not found", "createdAt": "2026-03-12T10:30:00.000Z", "completedAt": null }}Signature verification
Section titled “Signature verification”Every webhook delivery includes an X-Pageflare-Signature header containing an HMAC-SHA256 signature of the raw request body, keyed with your webhook secret.
Your webhook secret is shown once when you create the endpoint. Store it as an environment variable — never commit it to source control.
Verification algorithm
Section titled “Verification algorithm”- Read the raw request body as bytes (do not parse JSON first).
- Compute
HMAC-SHA256(secret, body). - Compare the hex digest to the value in
X-Pageflare-Signature. - Reject the request if the signatures do not match.
Node.js example:
import crypto from 'node:crypto';
function verifySignature(secret, rawBody, signatureHeader) { const expected = crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex'); // Use timingSafeEqual to prevent timing attacks const a = Buffer.from(expected, 'utf8'); const b = Buffer.from(signatureHeader, 'utf8'); if (a.length !== b.length) return false; return crypto.timingSafeEqual(a, b);}Python example:
import hashlibimport hmac
def verify_signature(secret: str, raw_body: bytes, signature_header: str) -> bool: expected = hmac.new( secret.encode(), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature_header)Always use a constant-time comparison (e.g., timingSafeEqual / hmac.compare_digest) to prevent timing attacks.
Retry policy
Section titled “Retry policy”If your endpoint does not respond with a 2xx status within 10 seconds, Pageflare treats the delivery as failed and retries with exponential backoff:
| Attempt | Delay after previous failure |
|---|---|
| 1 (initial) | — |
| 2 | 30 seconds |
| 3 | 5 minutes |
| 4 | 30 minutes |
After 4 failed attempts the event is marked as undeliverable. You can view failed deliveries and replay them manually from the dashboard under Settings → Webhooks → Delivery log.
To keep retry latency low, respond immediately with 200 OK and process the event asynchronously (e.g., push to a queue).
Testing webhooks locally
Section titled “Testing webhooks locally”During development, use a tunnel tool to expose your local server:
# Using ngrokngrok http 3000
# Using cloudflaredcloudflared tunnel --url http://localhost:3000Paste the public URL into the webhook settings in your dashboard, then trigger a test event with the Send test button.