Developers/Guides

Webhooks

Webhooks deliver the event catalogue to HTTPS endpoints you register in the portal (Developers, Webhooks) or through the API (up to 20 per organisation, each with its own secret shown once).

Endpoints

POST /v1/webhooks with the URL, the subscription and optional filters:

JSON
{
  "url": "https://example.com/postbuddy",
  "description": "CRM sync",
  "events": ["item.*", "postbatch.completed"],
  "filters": { "isTest": false }
}

The subscription names event types, families (item.*) or everything (*); filters narrow deliveries to a Postflow, a Postbatch or to live or test items. The response carries secret once. GET /v1/webhooks/{id}/deliveries lists the delivery log with the attempt history and the payload; POST /v1/webhooks/{id}/deliveries/{deliveryId}/replay sends an event again; POST /v1/webhooks/{id}/test sends a webhook.test envelope. A disabled endpoint is re-enabled with PATCH {"active": true}; POST /v1/webhooks/{id}/replay-missed then delivers the subscribed events of the disabled period.

Event feed

Integrations that cannot receive webhooks poll GET /v1/events (newest first, cursor pagination, 30 days of retention, filters on type and a period). The feed uses the same envelope as the deliveries.

Delivery

HTTP
POST https://example.com/postbuddy
X-Webhook-Id: whd_01J…
X-Webhook-Event: item.ready
X-Webhook-Timestamp: 1790000000
X-Webhook-Signature: v1=3f9a…
Content-Type: application/json

{ "id": "evt_01J…", "type": "item.ready", "version": 1, "createdAt": "…", "organisationId": "org_…", "isTest": false, "data": { "item": { "id": "itm_…", "status": "READY" } } }

Answer any 2xx within 10 seconds. Failed deliveries are retried with exponential backoff over 24 hours (1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h); after ten consecutive exhausted events the endpoint is disabled and Developers are notified.

Verify the signature

The signature is v1= plus the hex HMAC-SHA256 of timestamp + "." + rawBody with the endpoint secret; reject timestamps older than five minutes. Always verify on the raw body, before parsing.

TypeScript
import { verifyWebhookSignature } from '@postbuddy/sdk/webhooks';

app.post('/postbuddy', express.raw({ type: 'application/json' }), (req, res) => {
  try {
    const event = verifyWebhookSignature({
      secret: process.env.WEBHOOK_SECRET!,
      headers: req.headers,
      rawBody: req.body,
    });
    handle(event);
    res.sendStatus(204);
  } catch {
    res.sendStatus(400);
  }
});
Python
from postbuddy import verify_webhook_signature, WebhookSignatureError

try:
    event = verify_webhook_signature(secret, request.headers, request.get_data())
except WebhookSignatureError:
    abort(400)

Handlers must be idempotent: the same event id can be delivered more than once.