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.