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

POST/api/v1/webhooksCreate webhook
GET/api/v1/webhooksList webhooks
GET/api/v1/webhooks/:idGet webhook details
PATCH/api/v1/webhooks/:idUpdate webhook
DELETE/api/v1/webhooks/:idDelete webhook
POST/api/v1/webhooks/:id/testSend test event

Create webhook

POST/api/v1/webhooksCreate webhook

Register a new webhook endpoint. Schedulala will send POST requests to this URL whenever subscribed events occur.

Request Body

url
stringRequired

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)

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

GET/api/v1/webhooksList webhooks

Retrieve 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

GET/api/v1/webhooks/:idGet webhook details

Retrieve full details for a specific webhook, including recent delivery history. Use this to debug delivery failures and inspect response codes.

Path Parameters

id
stringRequired

The 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

PATCH/api/v1/webhooks/:idUpdate webhook

Update a webhook's URL, subscribed events, or active status. All fields are optional — only include the ones you want to change.

Path Parameters

id
stringRequired

The webhook ID

Request Body

url
stringOptional

New delivery URL

events
string[]Optional

New list of event types to subscribe to (replaces existing events)

isActive
booleanOptional

Set 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

DELETE/api/v1/webhooks/:idDelete webhook

Permanently delete a webhook endpoint. Schedulala will immediately stop delivering events to this URL.

Path Parameters

id
stringRequired

The 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

POST/api/v1/webhooks/:id/testSend test event

Send 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

id
stringRequired

The webhook ID to test

Request Body

event
stringRequired

The 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.

EventDescription
post.scheduledPost was successfully scheduled
post.publishedPost was published to platform(s)
post.failedPost failed to publish
post.cancelledPost was cancelled
post.updatedA scheduled or draft post was updated
thread.scheduledThread was successfully scheduled
thread.publishedAll posts in thread published successfully
thread.partially_publishedSome posts in thread failed
thread.failedThread failed to publish
account.connectedNew account connected via OAuth
account.disconnectedAccount was disconnected
account.errorAccount connection error (token expired, etc.)
account.token_expiringAccount 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.

Example payload (post.published)
{
"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 handler
app.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 event
const { 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:

AttemptDelayNotes
1st retry2 secondsImmediate retry for transient failures
2nd retry4 secondsAllows time for server recovery
3rd retry8 secondsFinal 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.

Next steps