DocsReference

Webhooks

Subscribe a URL to receive real-time events when posts are published, partially published or fail. We sign every delivery with HMAC-SHA256 so you can verify the request came from us.

Event types

EventFires when
post.queuedPost has been accepted and tasks enqueued.
post.publishedAll platforms succeeded (also emitted per platform).
post.partialAt least one platform succeeded and at least one failed.
post.failedEvery targeted platform failed, or a single platform terminally failed.
account.connectedUser connected a platform account.
account.disconnectedUser disconnected a platform account.

Register a webhook

POST /v1/webhooks

curl https://api.letspost.app/v1/webhooks \
  -H "Authorization: Bearer $SOCIALHUB_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your.app/letspost/webhook",
    "events": ["post.published","post.failed","post.partial"]
  }'

The response includes a secret — save it. Use it to verify every future delivery.

Delivery format

POST https://your.app/letspost/webhook
Content-Type: application/json
X-LetsPost-Event: post.published
X-LetsPost-Signature: sha256=<hex-hmac>

{
  "id":        "evt_1abcd23_4ef5",
  "type":      "post.published",
  "createdAt": "2026-04-16T20:30:01.000Z",
  "data":      { "postId": "pst_1", "published": 3, "failed": 0, "total": 3 }
}

Verify the signature

import express from 'express';
import crypto from 'node:crypto';

const app = express();
// Use the raw body — json parsing changes whitespace and breaks the HMAC.
app.post('/letspost/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.header('X-LetsPost-Signature') || '';
  const presented = header.replace(/^sha256=/, '');
  const expected = crypto
    .createHmac('sha256', process.env.SOCIALHUB_WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(presented), Buffer.from(expected))) {
    return res.status(401).end();
  }

  const event = JSON.parse(req.body.toString());
  console.log(event.type, event.data);
  res.status(200).end();
});

Retries & idempotency

  • Return a 2xx within 5s. Non-2xx or timeout is treated as failure.
  • We do not retry failed deliveries today — add your own queue if you need strict delivery guarantees (see roadmap). event.id
  • Deduplicate on event.id — it is unique per delivery.

Listing & revoking

GET /v1/webhooks lists your webhooks. DELETE /v1/webhooks/{id} removes one.

Was this page helpful?

Something unclear? Email us — we read every message.