Skip to content

Webhooks

Webhooks let your application receive real-time HTTP notifications when optimization jobs change state, without polling the Sites API.

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-Signature header for verification (see Signature verification)
  • An X-Pageflare-Event header naming the event type
EventTriggered when
site.completedAn optimization job reaches complete status
site.erroredAn optimization job reaches errored status

All webhook payloads share a common envelope:

{
"id": "evt_abc123",
"type": "site.completed",
"created_at": "2026-03-12T10:30:12.000Z",
"data": { ... }
}
FieldTypeDescription
idstringUnique event ID
typestringEvent type (see above)
created_atstringISO 8601 timestamp of when the event was generated
dataobjectEvent-specific payload
{
"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"
}
}
{
"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
}
}

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.

  1. Read the raw request body as bytes (do not parse JSON first).
  2. Compute HMAC-SHA256(secret, body).
  3. Compare the hex digest to the value in X-Pageflare-Signature.
  4. 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 hashlib
import 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.


If your endpoint does not respond with a 2xx status within 10 seconds, Pageflare treats the delivery as failed and retries with exponential backoff:

AttemptDelay after previous failure
1 (initial)
230 seconds
35 minutes
430 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).


During development, use a tunnel tool to expose your local server:

Terminal window
# Using ngrok
ngrok http 3000
# Using cloudflared
cloudflared tunnel --url http://localhost:3000

Paste the public URL into the webhook settings in your dashboard, then trigger a test event with the Send test button.