Skip to main content

Webhooks

SchedPilot sends an HTTP POST request to a URL of your choice whenever a post event occurs. Webhooks let your application react to publish outcomes in real time without polling the API.

Setup

  1. Log in to app.schedpilot.com and go to API Access.
  2. Find the Webhooks section.
  3. Enter your endpoint URL (must be https://).
  4. Click Save.
  5. Copy your signing secret — you will need it to verify incoming requests.
HTTPS required

SchedPilot only delivers webhooks to https:// endpoints. Plain http:// URLs are rejected at save time.

Events

EventTriggered when
post.publishedA scheduled post was successfully published to the social platform.
post.failedA publish attempt was made but the platform rejected it or an error occurred.

Payload Structure

All webhook requests share the same Content-Type: application/json header. The event field indicates which event occurred.

post.published

{
"event": "post.published",
"post_id": 1024,
"content": "Hello world! Check out our latest update.",
"platform": "twitter",
"account_id": 7,
"platform_post_id": "1801234567890123456",
"scheduled_date": "2024-06-15 09:00:00",
"published_at": "2024-06-15T09:00:05Z"
}
FieldTypeDescription
eventstringAlways "post.published".
post_idintegerSchedPilot internal post ID.
contentstringThe text content of the post.
platformstringThe social platform (e.g. "twitter", "linkedin", "instagram").
account_idintegerThe connected account that published the post.
platform_post_idstringThe native post ID assigned by the social platform.
scheduled_datestringThe original scheduled date/time (UTC).
published_atstringISO 8601 UTC timestamp of when the post was actually published.

post.failed

{
"event": "post.failed",
"post_id": 1025,
"content": "New product launch tomorrow!",
"platform": "instagram",
"account_id": 12,
"scheduled_date": "2024-06-15 10:30:00",
"error_message": "The access token has expired. Please reconnect your Instagram account."
}
FieldTypeDescription
eventstringAlways "post.failed".
post_idintegerSchedPilot internal post ID.
contentstringThe text content of the post.
platformstringThe social platform that rejected the publish attempt.
account_idintegerThe connected account that was used.
scheduled_datestringThe original scheduled date/time (UTC).
error_messagestringHuman-readable description of what went wrong.

Signature Verification

Every webhook request includes an X-SchedPilot-Signature header. The value is the HMAC-SHA256 of the raw request body, computed using your signing secret as the key, and hex-encoded.

Always verify this signature before processing the payload. Reject any request where verification fails — it was not sent by SchedPilot.

Where to find your signing secret

Go to app.schedpilot.com > API Access > Webhooks. Your signing secret is displayed below your endpoint URL.

Verification in Node.js

const crypto = require('crypto');

function verifySchedPilotSignature(rawBody, signatureHeader, signingSecret) {
const expected = crypto
.createHmac('sha256', signingSecret)
.update(rawBody)
.digest('hex');

// Use timingSafeEqual to prevent timing attacks
const expectedBuf = Buffer.from(expected, 'hex');
const receivedBuf = Buffer.from(signatureHeader, 'hex');

if (expectedBuf.length !== receivedBuf.length) {
return false;
}

return crypto.timingSafeEqual(expectedBuf, receivedBuf);
}

// Express example — important: use express.raw() so the body is a Buffer
const express = require('express');
const app = express();

app.post('/webhooks/schedpilot', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-schedpilot-signature'];
const signingSecret = process.env.SCHEDPILOT_SIGNING_SECRET;

if (!verifySchedPilotSignature(req.body, signature, signingSecret)) {
return res.status(401).send('Invalid signature');
}

const event = JSON.parse(req.body.toString());

if (event.event === 'post.published') {
console.log(`Post ${event.post_id} published to ${event.platform}`);
// Handle successful publish...
} else if (event.event === 'post.failed') {
console.error(`Post ${event.post_id} failed: ${event.error_message}`);
// Notify your team, trigger a retry flow, etc.
}

res.sendStatus(200);
});

Verification in PHP

<?php

function verifySchedPilotSignature(string $rawBody, string $signatureHeader, string $signingSecret): bool {
$expected = hash_hmac('sha256', $rawBody, $signingSecret);
return hash_equals($expected, $signatureHeader);
}

$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SCHEDPILOT_SIGNATURE'] ?? '';
$signingSecret = getenv('SCHEDPILOT_SIGNING_SECRET');

if (!verifySchedPilotSignature($rawBody, $signature, $signingSecret)) {
http_response_code(401);
exit('Invalid signature');
}

$event = json_decode($rawBody, true);

if ($event['event'] === 'post.published') {
// Handle successful publish
error_log("Post {$event['post_id']} published to {$event['platform']}");
} elseif ($event['event'] === 'post.failed') {
// Handle failure
error_log("Post {$event['post_id']} failed: {$event['error_message']}");
}

http_response_code(200);

Responding to Webhook Requests

  • Your endpoint must return an HTTP 200 status code within 5 seconds.
  • Any non-2xx response (or a timeout) is considered a delivery failure.
  • Retries are not currently implemented. If your endpoint is down when an event fires, the delivery will be lost. Plan accordingly — for example, log all incoming events immediately before doing any heavy processing.
Async processing tip

To stay within the 5-second window, acknowledge the request right away and hand off the actual work to a background job or queue.