Webhooks
Receive real-time notifications when events happen in your SocialRails workspace via webhooks.
Webhooks let your application receive real-time HTTP notifications when events occur in your SocialRails workspace, like when a post is published, fails to publish, or gets scheduled.
Event Types
Register a Webhook
POST /api/v1/webhooks
Requires the webhooks scope on your API key.
Request Body
{
"url": "https://your-app.com/webhooks/socialrails",
"events": ["post.published", "post.failed"]
}Example
curl -X POST https://socialrails.com/api/v1/webhooks \
-H "Authorization: Bearer sr_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/socialrails",
"events": ["post.published", "post.failed"]
}'Response
{
"data": {
"id": "wh_abc123",
"url": "https://your-app.com/webhooks/socialrails",
"events": ["post.published", "post.failed"],
"secret": "whsec_a1b2c3d4e5f67890abcdef",
"is_active": true,
"created_at": "2026-03-02T12:00:00Z"
}
}Save the secret — it's shown only once and is used to verify webhook payloads.
List Webhooks
GET /api/v1/webhooks
curl https://socialrails.com/api/v1/webhooks \
-H "Authorization: Bearer sr_live_YOUR_KEY"Delete a Webhook
DELETE /api/v1/webhooks/:id
curl -X DELETE https://socialrails.com/api/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer sr_live_YOUR_KEY"Webhook Payload Format
When an event occurs, SocialRails sends a POST request to your registered URL with these headers:
Payload body
{
"event": "post.published",
"timestamp": "2026-03-04T14:30:00Z",
"data": {
"post_id": "abc-123",
"workspace_id": "ws-456",
"platform": "twitter,linkedin",
"status": "published",
"posted_at": "2026-03-04T14:30:00Z",
"platform_results": [
{ "platform": "twitter", "status": "success" },
{ "platform": "linkedin", "status": "success" }
]
}
}Verifying Webhook Signatures
Every webhook delivery is signed with HMAC-SHA256 using your webhook's secret. Always verify the signature before processing the payload to ensure it came from SocialRails.
The signature is computed over {timestamp}.{raw_body} where timestamp is the value of the X-SocialRails-Timestamp header and raw_body is the raw JSON request body.
JavaScript (Node.js)
function verifyWebhook(req, secret) {
const signature = req.headers['x-socialrails-signature'];
const timestamp = req.headers['x-socialrails-timestamp'];
const body = req.rawBody; // raw request body string
// Reject if timestamp is older than 5 minutes (replay protection)
const age = Math.abs(Date.now() / 1000 - parseInt(timestamp, 10));
if (age > 300) {
return false;
}
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${body}`)
.digest('hex');
const expectedSig = `sha256=${expected}`;
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSig)
);
}
// Express example
app.post('/webhooks/socialrails', express.raw({ type: 'application/json' }), (req, res) => {
const secret = process.env.SOCIALRAILS_WEBHOOK_SECRET;
if (!verifyWebhook(req, secret)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log(`Received ${event.event}:`, event.data);
// Process the event...
res.status(200).send('ok');
});Python (Flask)
import hmac
import hashlib
import time
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your_secret_here"
def verify_webhook(req):
signature = req.headers.get("X-SocialRails-Signature", "")
timestamp = req.headers.get("X-SocialRails-Timestamp", "")
body = req.get_data(as_text=True)
# Reject if timestamp is older than 5 minutes
age = abs(time.time() - int(timestamp))
if age > 300:
return False
expected = hmac.new(
WEBHOOK_SECRET.encode(),
f"{timestamp}.{body}".encode(),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(signature, f"sha256={expected}")
@app.route("/webhooks/socialrails", methods=["POST"])
def handle_webhook():
if not verify_webhook(request):
abort(401, "Invalid signature")
event = request.get_json()
print(f"Received {event['event']}: {event['data']}")
# Process the event...
return "ok", 200Tips
- Always verify signatures before processing webhook data.
- Use timing-safe comparison (
crypto.timingSafeEqualin Node.js,hmac.compare_digestin Python) to prevent timing attacks. - Check the timestamp and reject requests older than 5 minutes to prevent replay attacks.
- Respond with
2xxquickly — do heavy processing asynchronously. SocialRails considers any non-2xx response a failure. - Idempotency — your handler may receive the same event twice (e.g. on retry). Use
data.post_id+eventas a deduplication key.
Retry Behavior
Failed deliveries (network error or 5xx response) are retried once after 2 seconds. After that, the delivery is marked as failed in the webhook_deliveries log.
You can inspect delivery history in the dashboard (coming soon) or by querying the webhook_deliveries table if you have database access.
Security
- Webhook URLs must use HTTPS
- Each webhook has a unique
secretfor signature verification - Maximum 10 webhooks per workspace
- All deliveries are logged with status, HTTP response code, and latency
Limits
- Scope required:
webhooks(for creating/deleting) - Read access:
readscope is sufficient to list webhooks - Maximum 10 webhooks per workspace