Webhooks
Instead of polling GET /v0/posts/{id} or GET /v0/studio/getGeneration, register a webhook endpoint and Schedulin will POST to your URL when things happen.
Events
| Event | Fires when |
|---|---|
post.published | A post finished publishing to its platform |
post.failed | A publish attempt terminally failed |
generation.completed | An AI image, video, or portrait generation finished rendering |
generation.failed | An AI generation failed |
Register an endpoint
curl -X POST https://api.schedulin.app/v0/webhooks \ -H "x-api-key: $SCHEDULIN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/hooks/schedulin", "events": ["post.published", "post.failed"], "description": "Production notifications" }'
The response includes the signing secret (whsec_...) once — store it. Subsequent reads return a masked value. Rotate it any time with POST /v0/webhooks/{id}/rotate-secret.
URLs must be https. Each organization can register up to 10 endpoints. OAuth apps need the webhooks:read / webhooks:write scopes.
Delivery format
{ "id": "d2f1…", "event": "post.published", "createdAt": "2026-07-28T15:04:05.000Z", "data": { "postId": "…", "socialAccountId": "…", "platform": "TIKTOK", "status": "COMPLETED" } }
Headers on every delivery:
| Header | Meaning |
|---|---|
x-schedulin-event | The event name |
x-schedulin-delivery-id | Unique delivery id (use for idempotency) |
x-schedulin-signature | t=<unix seconds>,v1=<hex HMAC-SHA256> |
Verifying signatures
The signature is an HMAC-SHA256 of `${t}.${rawBody}` using your endpoint secret:
import { createHmac, timingSafeEqual } from "node:crypto"; function verify(rawBody: string, header: string, secret: string): boolean { const parts = Object.fromEntries( header.split(",").map((kv) => kv.split("=") as [string, string]), ); const expected = createHmac("sha256", secret) .update(`${parts.t}.${rawBody}`) .digest("hex"); const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; return ( fresh && timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(parts.v1, "hex")) ); }
Reject deliveries older than 5 minutes to prevent replay.
Retries and auto-disable
Respond with any 2xx within 10 seconds. Anything else (including timeouts) is retried with exponential backoff for up to 5 attempts. After 15 consecutive terminally-failed deliveries the endpoint is disabled (enabled: false); fix your receiver and re-enable it with PATCH /v0/webhooks/{id}.
Testing
POST /v0/webhooks/{id}/test sends a signed ping event to your URL. Delivery history — status, attempts, last response code — is at GET /v0/webhooks/{id}/deliveries.