Public APIWebhooks

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

EventDescription
post.publishedA scheduled post was successfully published to the platform
post.failedA scheduled post failed to publish
post.scheduledA new post was scheduled (via API or dashboard)

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:

HeaderDescription
X-SocialRails-EventEvent name (e.g. post.published)
X-SocialRails-TimestampUnix timestamp (seconds) when the request was sent
X-SocialRails-SignatureHMAC-SHA256 signature: sha256={hex}
Content-Typeapplication/json

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", 200

Tips

  • Always verify signatures before processing webhook data.
  • Use timing-safe comparison (crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python) to prevent timing attacks.
  • Check the timestamp and reject requests older than 5 minutes to prevent replay attacks.
  • Respond with 2xx quickly — 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 + event as 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 secret for 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: read scope is sufficient to list webhooks
  • Maximum 10 webhooks per workspace
Updated 3 days ago