How it works
- Register a webhook in the dashboard at Settings →
Developers → Webhooks (or via the dashboard’s
me/webhooksendpoints). Pick the events you want, paste your HTTPS URL, and Postbreeze generates a signing secret. - Postbreeze signs every delivery with an HMAC-SHA256 of the request body. Verify the signature in your handler before doing anything with the payload.
- You respond
2xxwithin 10 seconds. Anything else is a failure and triggers the retry schedule below.
Webhooks are outbound only — Postbreeze pushes events to your
endpoint. You don’t poll for them, and you don’t need an API key on
your endpoint.
Quick start
handler.ts
Request format
Every delivery is aPOST with Content-Type: application/json. The
body is the same shape for every event — the per-event detail lives
on payload.
Headers Postbreeze sets
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | Postbreeze-Webhook/1.0 |
X-Postbreeze-Event | Event key, e.g. post.published |
X-Postbreeze-Delivery-Id | Stable per-delivery id. Use this for deduplication |
X-Postbreeze-Api-Version | 2026-05-01 (matches apiVersion in the body) |
X-Postbreeze-Signature | t=<unix-seconds>,v1=<hex-hmac> |
X-Internal-Token: …) — Postbreeze merges them in but cannot
override the default headers above.
Signature verification
TheX-Postbreeze-Signature header has the form:
tis the Unix timestamp in seconds when Postbreeze signed the request.v1is the HMAC-SHA256 of<t>.<rawBody>using your webhook’s signing secret, hex-encoded.
- Split the header by
,and readt+v1. - Compute
HMAC-SHA256("<t>.<rawBody>", secret). - Compare in constant time.
- Reject if the timestamp is more than 5 minutes off the current time (replay protection).
Rotating the secret
Rotate the signing secret from Settings → Developers → Webhooks → Rotate secret. During the rotation window (24 hours by default), Postbreeze signs every delivery with both secrets:v1 value. After the window closes the old secret
stops being attached.
Idempotency & deduplication
Webhooks are delivered at least once. A receiver that times out on attempt 1 and succeeds on attempt 2 receives the same event twice. UseX-Postbreeze-Delivery-Id as your deduplication key — it’s
stable across retries of the same delivery. The cheapest pattern is
a uniquely-indexed table:
ack and skip.
Retry policy
A delivery is successful when your endpoint returns a2xx
response within 10 seconds. Anything else fails the attempt.
| Attempt | Delay before next attempt |
|---|---|
| 1 | immediate |
| 2 | 30 seconds |
| 3 | 5 minutes |
| 4 | 1 hour |
| 5 | 6 hours |
| 6 | 24 hours |
Which failures retry?
| Outcome | Behavior |
|---|---|
408, 429, 5xx | Retried per the schedule above |
| Network error / timeout | Retried |
401, 403, 404, 410 | Not retried (terminal — your endpoint is rejecting or gone) |
Any other 4xx | Not retried (your handler has a bug we can’t fix by waiting) |
3xx redirect | Not retried — Postbreeze refuses to follow redirects to defend against SSRF. Point your webhook URL at the final endpoint. |
Auto-disable
After 15 consecutive terminal failures Postbreeze automatically disables the webhook and fires a finalwebhook.disabled_by_system
event to the same endpoint (best-effort). Re-enable it from the
dashboard once the endpoint is back.
Event catalogue
All event keys, in canonical order. Schemas use TypeScript-style notation —string | null means nullable.
Posts
| Event | When it fires |
|---|---|
post.scheduled | A post is scheduled to publish in the future |
post.published | All targets of a post published successfully |
post.failed | All targets of a post failed |
post.partial | Some targets succeeded, others failed |
post.cancelled | A scheduled post was canceled before publish |
post.platform.published | One target of a multi-platform post landed |
post.platform.failed | One target of a multi-platform post failed |
post.published payload
post.partial payload
post.platform.published payload
post.platform.failed payload
Accounts
| Event | When it fires |
|---|---|
account.connected | A social account is connected to a workspace |
account.disconnected | A connected account is disconnected (user-initiated or revoked externally) |
account.token_expired | The platform refresh token expired and the account is now read-only until reconnect |
account.connected payload
account.disconnected payload
Comments
| Event | When it fires |
|---|---|
comment.received | A new inbox comment is received (Instagram / X / YouTube / Facebook) |
comment.received payload
Webhook lifecycle
These are meta-events about the webhook itself — useful for detecting your endpoint being auto-disabled.| Event | When it fires |
|---|---|
webhook.test | Triggered by the “Send test event” button in the dashboard |
webhook.disabled_by_system | Fired once when consecutive failures cross the auto-disable threshold |
webhook.disabled_by_system payload
Best practices
- Verify the signature on every delivery. Don’t skip in development.
- Deduplicate by
deliveryId. Treat at-least-once as a guarantee, not a wish. - Acknowledge quickly. Return
2xxwithin 10 seconds; queue heavy work for a background worker. - Treat events as notifications, not state. If you missed a delivery (auto-disable, your endpoint was down), re-fetch from the API to reconcile — never trust the webhook to be your only source of truth.
- Lock the receiver down. Reject requests that aren’t a
POSTwithContent-Type: application/json. Use the signing secret as your only authority — don’t IP-allowlist Postbreeze. - Handle the
disabled_by_systemevent explicitly. Page on it; otherwise you’ll only notice events have stopped flowing when something downstream breaks. - Use the dashboard’s “Deliveries” tab to inspect recent failures — every attempt logs its HTTP status, response body (truncated), and the request headers we sent.
Limits
- Max 50 webhooks per user
- Max payload size persisted: 64 KB (large payloads still deliver, but the response/request bodies stored for the deliveries log are truncated)
- Custom headers: up to 10 per webhook, max 1 KB total
- URL must be HTTPS —
http://is rejected at create time - URL must resolve to a public IP — RFC1918, link-local, and IMDS hostnames are rejected at both create time and delivery time (DNS rebinding protection)
Managing webhooks
Webhooks are managed from the dashboard at Settings → Developers → Webhooks — create, rotate secrets, disable, send a test event, and browse delivery history. The management endpoints are cookie-only (/me/webhooks); API keys can’t mint or revoke webhooks.