Webhooks
Subscribe to post lifecycle events and verify delivery signatures.
Webhooks
Postato pushes events to HTTPS endpoints you register. Use them to react to post delivery, approval decisions, and account status changes without polling.
Register a webhook
curl -X POST https://api.postato.com.br/v1/workspaces/$WORKSPACE_ID/webhooks \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/postato",
"events": ["post.published", "post.failed"]
}'The response includes a secret; store it. You won't see it again. It's the key used to verify incoming signatures.
Signature verification
Every outbound delivery carries:
X-Webhook-Signature: t=1712345678,v1=4a7c9e2f0b1d3a8c...
Content-Type: application/jsonThe signature is HMAC-SHA256(secret, "{timestamp}.{body}") (Stripe-style). Verify in your handler:
import crypto from 'node:crypto';
function verify(req: Request, secret: string): boolean {
const header = req.headers['x-webhook-signature'] as string;
const [tPart, v1Part] = header.split(',');
const timestamp = tPart.split('=')[1];
const signature = v1Part.split('=')[1];
const payload = `${timestamp}.${req.rawBody}`;
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) return false;
// Reject anything older than 5 minutes to prevent replay.
const skew = Math.abs(Date.now() / 1000 - Number(timestamp));
return skew <= 300;
}Use timingSafeEqual instead of === to avoid side-channel leaks.
Events
| Event | When |
|---|---|
post.queued | Post accepted and waiting for delivery. |
post.published | Successfully delivered to the target network. |
post.failed | Final failure after retries; payload includes error detail. |
post.scheduled | Scheduled post accepted; will fire at scheduledAt. |
approval.requested | A post entered an approval gate. |
approval.decided | A reviewer approved or rejected. |
account.disconnected | OAuth token revoked or refresh failure. |
Delivery guarantees
- At-least-once delivery. Make your handler idempotent.
- Exponential backoff retries for non-2xx responses, up to ~24 hours.
- Delivery history is available via
GET /webhooks/{id}/deliveries.
Responding
Return 2xx as soon as you've durably accepted the payload. Don't do expensive work synchronously; enqueue and respond, then process on your side. Anything above 5 seconds is considered failed and retried.
Rotating the secret
Delete the webhook and create a new one. There is no in-place secret rotation (this is intentional). Rotating implies a clear switchover window, which you control by running the old endpoint alongside the new one briefly.