Webhooks
post.buzz handles webhooks in two directions:
- Inbound — platforms (Meta, TikTok, YouTube, Threads, Telegram, Slack, LinkedIn) POST events to post.buzz so we can update posts, ingest comments/DMs, and sync insights. These are configured by post.buzz and not something you set up.
- Outbound — post.buzz POSTs events about your activity to HTTPS URLs you configure, signed with a per-webhook secret, with automatic retries and a browsable delivery log.
Outbound webhooks
When you want post.buzz events in your own system, configure an outbound webhook in the app (Settings → Webhooks). post.buzz POSTs event payloads to your endpoint, records each delivery (with response status and attempts) so you can audit, and lets you redeliver any delivery manually.
Endpoint requirements
- The URL must be HTTPS.
- It must not point at private/loopback ranges — localhost,
127.,10.,172.16–31.,192.168.,169.254.,::1,fc00::/7,fe80::/10are all rejected (SSRF protection). - You subscribe the endpoint to at least one event from the catalog below.
Outbound events
Eight events are available:
| Event | Fires when |
|---|---|
post.scheduled | A post is scheduled (created or rescheduled) |
post.published | A post finishes publishing to all its accounts |
post.failed | A publish attempt fails (all or some accounts) |
message.sent | An inbox reply is sent successfully |
message.failed | An inbox reply fails to send |
message.deleted | A sent reply is deleted on the platform |
message.delivered | A message is confirmed delivered (platform watermark) |
message.read | A message is confirmed read (platform watermark) |
Payload shapes
// post.scheduled
{ "postId": "pst_abc123", "accountIds": ["acc_1"], "scheduledAt": "2026-07-15T14:30:00Z" }
// post.published
{ "postId": "pst_abc123", "accountIds": ["acc_1"], "publishedAt": "2026-07-15T14:30:05Z" }
// post.failed (errorMessage: "All schedules failed" | "Some schedules failed")
{
"postId": "pst_abc123",
"accountIds": ["acc_1"],
"publishedAt": "2026-07-15T14:30:05Z",
"errorMessage": "Some schedules failed"
}
// message.sent
{
"inboxItemId": "inb_01",
"accountId": "acc_1",
"platform": "instagram",
"recipientPlatformId": "17841...",
"messageId": "cm_9",
"message": "It ships today! 🚀",
"occurredAt": "2026-07-15T12:00:00Z"
}
// message.failed (same fields, plus error)
{
"inboxItemId": "inb_01",
"accountId": "acc_1",
"platform": "instagram",
"recipientPlatformId": "17841...",
"messageId": null,
"message": "It ships today! 🚀",
"error": "Platform rejected the reply",
"occurredAt": "2026-07-15T12:00:00Z"
}
// message.deleted
{ "accountId": "acc_1", "platform": "instagram", "messageId": "cm_9", "occurredAt": "2026-07-15T12:05:00Z" }
// message.delivered (same as deleted, plus watermark)
{ "accountId": "acc_1", "platform": "instagram", "messageId": "cm_9", "watermark": 1784100000000, "occurredAt": "2026-07-15T12:02:00Z" }
// message.read
{ "accountId": "acc_1", "platform": "instagram", "watermark": 1784100000000, "occurredAt": "2026-07-15T12:03:00Z" }
Delivery request
Each delivery is a POST to your URL with:
| Header | Value |
|---|---|
Content-Type | application/json |
X-Webhook-Event | The event name (e.g. post.published) |
X-Webhook-Delivery-Id | Unique ID for this delivery attempt batch |
X-Webhook-Signature | Hex-encoded HMAC-SHA256 of the exact request body using the webhook's signing secret |
Any 2xx response counts as delivered. post.buzz waits at most 10 seconds
per attempt.
Verifying the signature
import { createHmac, timingSafeEqual } from "node:crypto";
const expected = createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(rawBody) // the exact bytes, before JSON.parse
.digest("hex");
const given = req.headers["x-webhook-signature"];
const expectedBytes = Buffer.from(expected, "utf8");
const receivedBytes = Buffer.from(
typeof given === "string" ? given : "",
"utf8",
);
const ok =
expectedBytes.length === receivedBytes.length &&
timingSafeEqual(expectedBytes, receivedBytes);
if (!ok) {
return res.status(401).send("Invalid webhook signature");
}
Compute the HMAC over the raw request bytes — re-serialized JSON will not match. Compare in constant time.
Signing secret
Each webhook gets a signing secret (32 random bytes, hex) generated at creation. It is shown once, when you create the webhook — copy it immediately; post.buzz cannot show it again and never re-exposes it. If you lose it, delete the webhook and create a new one.
Retries
- 4 attempts total — the initial delivery plus 3 retries.
- Backoff between attempts: 1 s → 4 s → 16 s.
- A delivery that never gets a
2xxends asfailed; success ends asdelivered. Both are terminal. - Stuck
runningdeliveries are reclaimed after 5 minutes.
Delivery log & redelivery
Every attempt is recorded per webhook: payload, event, response status code, attempt count, and next retry time. Browse the log in Settings → Webhooks and use the per-delivery Retry button (or the webhook management API's redeliver action) to reset a delivery to pending and re-drive it immediately.
Inbound platform webhooks
These endpoints receive events from social platforms. They're part of post.buzz's backend and are listed here for transparency and for customers who need to verify callback URLs in a developer portal.
| Endpoint | Platforms | Purpose |
|---|---|---|
/api/meta/webhook | Instagram, Facebook, Threads (shared) | Comments, DMs, mentions, insights |
/api/threads/webhook | Threads | publish / replies / mentions (Threads-specific envelope) |
/api/tiktok/webhook | TikTok | Async publish completion/failure |
/api/youtube/webhook | YouTube | Comments (PubSubHubbub Atom feed) |
/api/telegram/webhook/{accountId} | Telegram | DMs and group messages (per-account route) |
/api/linkedin/webhook | Verification only (comments polled) |
Slack also delivers app mentions and bot DMs to /api/slack/events when
configured. Requests use Slack’s timestamped signature; unsigned, stale, or
invalid requests are rejected. Event subscriptions and a signing secret must
be configured for this handler to accept deliveries.
Security model
- Meta & Threads —
GETperforms the App Dashboard verification handshake (hub.mode=subscribe,hub.verify_token, echoeshub.challenge).POSTverifiesX-Hub-Signature-256(HMAC-SHA256 of the raw body keyed with the app secret). The Meta endpoint accepts any of the Facebook/Instagram/Threads secrets; Threads verifies against its own secret only. - TikTok — verifies
TikTok-Signature(HMAC-SHA256 over<timestamp>.<rawBody>keyed with the client secret). - Telegram — verifies
X-Telegram-Bot-Api-Secret-Tokenagainst the account's bot secret, compared in constant time. - YouTube —
GETechoes thehub.challenge;POSTingests the Atom XML. - Valid events are acknowledged after handling. Authentication or verification failures can return non-success responses. Do not treat an HTTP response alone as proof that a particular event was ingested.
Why LinkedIn is different
LinkedIn has no real-time comment webhook for personal pages (only for
organization pages via reviewed orgSocial subscriptions). Its webhook
endpoint exists so the callback URL is valid and returns 200 on pings, but
comment ingestion relies on cron polling of the LinkedIn adapter.
Related
- Unified inbox — where inbound comments/DMs land.
- Comment automation — acts on inbound events.
- Platforms — which platforms feed inbound webhooks.
- Examples — a full signature-verification recipe.