Webhooks API
Receive real-time notifications when posts are published, fail, or accounts have issues. Configure webhook endpoints and Schedulala will send POST requests to your URL with signature verification for every event you subscribe to.
Endpoints
/api/v1/webhooksCreate webhook/api/v1/webhooksList webhooks/api/v1/webhooks/:idGet webhook details/api/v1/webhooks/:idUpdate webhook/api/v1/webhooks/:idDelete webhook/api/v1/webhooks/:id/testSend test eventCreate webhook
/api/v1/webhooksCreate webhookRegister a new webhook endpoint. Schedulala will send POST requests to this URL whenever subscribed events occur.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
url | string | Required | The HTTPS URL to deliver webhook events to. Must be publicly accessible. |
events | string[] | Required | Array of event types to subscribe to (see events table below) |
urlstringRequiredThe HTTPS URL to deliver webhook events to. Must be publicly accessible.
eventsstring[]RequiredArray of event types to subscribe to (see events table below)
Schedulala generates the signing secret for you and returns it once in the creation response. Store it securely; you cannot retrieve it again later.
curl -X POST https://schedulala.com/api/v1/webhooks \-H "Authorization: Bearer sk_live_abc123xyz789..." \-H "Content-Type: application/json" \-d '{"url": "https://myapp.com/webhooks/schedulala","events": ["post.published", "post.failed", "account.error"]}'
{"success": true,"webhook": {"id": "wh_abc123","url": "https://myapp.com/webhooks/schedulala","events": ["post.published","post.failed","account.error"],"secret": "whsec_a1b2c3d4e5f6...","isActive": true,"createdAt": "2026-03-01T10:00:00Z"}}
Save the secret: The secret field is only included in this creation response. Use it to verify webhook signatures (see below).
List webhooks
/api/v1/webhooksList webhooksRetrieve all webhook endpoints configured on your account.
curl https://schedulala.com/api/v1/webhooks \-H "Authorization: Bearer sk_live_abc123xyz789..."
{"success": true,"webhooks": [{"id": "wh_abc123","url": "https://myapp.com/webhooks/schedulala","events": ["post.published","post.failed","account.error"],"isActive": true,"lastDeliveredAt": "2026-02-28T18:30:00Z"},{"id": "wh_def456","url": "https://myapp.com/webhooks/accounts","events": ["account.connected","account.disconnected"],"isActive": true,"lastDeliveredAt": null}]}
Get webhook details
/api/v1/webhooks/:idGet webhook detailsRetrieve full details for a specific webhook, including recent delivery history. Use this to debug delivery failures and inspect response codes.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Required | The webhook ID |
idstringRequiredThe webhook ID
curl https://schedulala.com/api/v1/webhooks/wh_abc123 \-H "Authorization: Bearer sk_live_abc123xyz789..."
{"success": true,"webhook": {"id": "wh_abc123","url": "https://myapp.com/webhooks/schedulala","events": ["post.published","post.failed","account.error"],"isActive": true,"lastDeliveredAt": "2026-02-28T18:30:00Z","consecutiveFailures": 0,"disabledAt": null,"disabledReason": null,"createdAt": "2026-02-01T09:00:00Z"},"deliveries": [{"id": "del_001","event": "post.published","status": "delivered","responseCode": 200,"responseTimeMs": 145,"retryCount": 0,"error": null,"deliveredAt": "2026-02-28T18:30:00Z"},{"id": "del_002","event": "post.failed","status": "delivered","responseCode": 200,"responseTimeMs": 98,"retryCount": 0,"error": null,"deliveredAt": "2026-02-28T16:15:00Z"},{"id": "del_003","event": "post.published","status": "failed","responseCode": 500,"responseTimeMs": 1042,"retryCount": 3,"error": "HTTP 500","deliveredAt": "2026-02-27T12:00:00Z"}]}
Update webhook
/api/v1/webhooks/:idUpdate webhookUpdate a webhook's URL, subscribed events, or active status. All fields are optional — only include the ones you want to change.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Required | The webhook ID |
idstringRequiredThe webhook ID
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
url | string | Optional | New delivery URL |
events | string[] | Optional | New list of event types to subscribe to (replaces existing events) |
isActive | boolean | Optional | Set to false to pause deliveries without deleting the webhook |
urlstringOptionalNew delivery URL
eventsstring[]OptionalNew list of event types to subscribe to (replaces existing events)
isActivebooleanOptionalSet to false to pause deliveries without deleting the webhook
curl -X PATCH https://schedulala.com/api/v1/webhooks/wh_abc123 \-H "Authorization: Bearer sk_live_abc123xyz789..." \-H "Content-Type: application/json" \-d '{"events": ["post.published", "post.failed", "account.error", "account.token_expiring"],"isActive": true}'
{"success": true,"webhook": {"id": "wh_abc123","url": "https://myapp.com/webhooks/schedulala","events": ["post.published","post.failed","account.error","account.token_expiring"],"isActive": true}}
Delete webhook
/api/v1/webhooks/:idDelete webhookPermanently delete a webhook endpoint. Schedulala will immediately stop delivering events to this URL.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Required | The webhook ID to delete |
idstringRequiredThe webhook ID to delete
curl -X DELETE https://schedulala.com/api/v1/webhooks/wh_abc123 \-H "Authorization: Bearer sk_live_abc123xyz789..."
{"success": true,"message": "Webhook deleted"}
Send test event
/api/v1/webhooks/:id/testSend test eventSend a test event to a webhook endpoint. The test event uses realistic sample data so you can verify your endpoint handles the payload correctly.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Required | The webhook ID to test |
idstringRequiredThe webhook ID to test
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
event | string | Required | The event type to simulate (e.g. post.published) |
eventstringRequiredThe event type to simulate (e.g. post.published)
curl -X POST https://schedulala.com/api/v1/webhooks/wh_abc123/test \-H "Authorization: Bearer sk_live_abc123xyz789..." \-H "Content-Type: application/json" \-d '{ "event": "post.published" }'
{"success": true,"test": {"event": "post.published","deliveredAt": "2026-03-01T10:00:00Z","responseCode": 200,"responseTime": 145}}
Tip: Use the test endpoint during development to verify your webhook handler processes the payload and returns a 200 response. Test events are signed with your webhook secret just like real events.
Webhook events
Subscribe to any combination of these events when creating or updating a webhook. Currently post.published and post.failed are delivered automatically when a post finishes publishing. The remaining event types can be subscribed to and exercised via the test endpoint, but are not yet emitted automatically.
| Event | Description |
|---|---|
post.scheduled | Post was successfully scheduled |
post.published | Post was published to platform(s) |
post.failed | Post failed to publish |
post.cancelled | Post was cancelled |
post.updated | A scheduled or draft post was updated |
thread.scheduled | Thread was successfully scheduled |
thread.published | All posts in thread published successfully |
thread.partially_published | Some posts in thread failed |
thread.failed | Thread failed to publish |
account.connected | New account connected via OAuth |
account.disconnected | Account was disconnected |
account.error | Account connection error (token expired, etc.) |
account.token_expiring | Account token expires within 7 days |
Webhook payload format
Every webhook delivery sends a JSON payload as the request body. The structure is consistent across all event types — only the data field varies based on the event.
{"id": "evt_abc123","event": "post.published","test": false,"createdAt": "2026-01-15T14:00:03Z","data": {"post": {"id": "post_789xyz","content": "Hello from the Schedulala API!","status": "published","platforms": [{"platform": "tiktok","status": "published","postId": "7123456789012345678","url": "https://tiktok.com/@user/video/7123456789012345678","postedAt": "2026-01-15T14:00:02Z"}],"publishedAt": "2026-01-15T14:00:03Z"}}}
Tip: For multi-platform posts, the platforms array includes an entry for each platform with its individual status. A post can succeed on some platforms and fail on others.
Signature verification
Every webhook request includes an X-Webhook-Signature header. Always verify this signature to confirm the request came from Schedulala and was not tampered with.
Signature format
The header value has the format:
X-Webhook-Signature: t=1234567890,v1=abc123def456...
The signature is computed as an HMAC-SHA256 hash of the signed payload string, which is built by concatenating the timestamp and the JSON-stringified request body separated by a dot:
signed_payload = "{timestamp}.{JSON.stringify(body)}"signature = HMAC-SHA256(signed_payload, webhook_secret)
Verification examples
import crypto from 'crypto';function verifyWebhookSignature(payload, signature, secret) {const [tPart, vPart] = signature.split(',');const timestamp = tPart.replace('t=', '');const expectedSig = vPart.replace('v1=', '');const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;const computed = crypto.createHmac('sha256', secret).update(signedPayload).digest('hex');return crypto.timingSafeEqual(Buffer.from(computed),Buffer.from(expectedSig));}// Usage in an Express handlerapp.post('/webhooks/schedulala', (req, res) => {const signature = req.headers['x-webhook-signature'];const isValid = verifyWebhookSignature(req.body,signature,process.env.WEBHOOK_SECRET);if (!isValid) {return res.status(401).json({ error: 'Invalid signature' });}// Process the eventconst { event, data } = req.body;console.log(`Received ${event}`, data);res.status(200).json({ received: true });});
Security: Always use a timing-safe comparison function (like crypto.timingSafeEqual in Node.js or hmac.compare_digest in Python) to prevent timing attacks. Never use === or == for signature comparison.
Retry behavior
If your endpoint returns a non-2xx response or is unreachable, Schedulala retries the delivery with exponential backoff:
| Attempt | Delay | Notes |
|---|---|---|
| 1st retry | 2 seconds | Immediate retry for transient failures |
| 2nd retry | 4 seconds | Allows time for server recovery |
| 3rd retry | 8 seconds | Final attempt |
After all 3 retries fail, the delivery is marked as failed in the webhook's delivery history. You can view failed deliveries in the webhook details endpoint. After 10 consecutive failed deliveries, the webhook is automatically disabled; re-enable it with PATCH and isActive: true, which also resets the failure count.
Best practice: Your webhook endpoint should return a 200 response as quickly as possible, then process the event asynchronously. If processing takes too long, the connection may time out and trigger unnecessary retries.