Webhooks
Get a signed HTTPS request when a post publishes or fails, a comment or message arrives, or an account needs reconnecting.
The workspace owner adds endpoints in Settings → Integrations → Webhooks, or with the API (up to 5), picks the events, and can send a test.
| Event | When | Payload |
|---|---|---|
post.published |
One account’s post went live, with the link | schema |
post.failed |
One account failed after its retries ran out, with the reason | schema |
post.handoff |
A TikTok video is waiting in the account’s TikTok inbox | schema |
comment.received |
A new comment (or a reply in a thread waiting for you) on Facebook or Instagram | schema |
message.received |
New direct messages on Facebook or Instagram | schema |
account.needs_reconnect |
An account lost access; posts to it fail until it’s reconnected | schema |
ping |
The test button, or POST /webhooks/{id}/test |
schema |
Post events are per account: a post to three accounts sends up to three post.published deliveries.
Payloads
Section titled “Payloads”Every delivery has event, deliveredAt and workspaceId. A post event:
{ "event": "post.published", "deliveredAt": "2026-11-03T02:00:41.512Z", "workspaceId": "ws_…", "post": { "id": "p_…", "caption": "Es kopi gula aren…", "scheduledAt": "2026-11-03T02:00:00.000Z" }, "target": { "id": "t_…", "platform": "instagram", "account": { "name": "Kopi Senja", "username": "kopisenja" }, "contentType": "ig_image", "status": "published", "permalink": "https://www.instagram.com/p/…", "externalPostId": "1789…", "publishedAt": "2026-11-03T02:00:39.000Z", "error": null }}A comment event:
{ "event": "comment.received", "deliveredAt": "2026-11-03T05:12:00.000Z", "workspaceId": "ws_…", "account": { "id": "acc_ig", "platform": "instagram", "name": "Kopi Senja", "username": "kopisenja" }, "comment": { "id": "cm_…", "isReply": false, "authorName": "dewi.s", "text": "Buka sampai jam berapa?", "commentedAt": "2026-11-03T05:09:41.000Z" }}Reply to it with POST /inbox/{comment.id}/reply. message.received carries the
conversation (reply with POST /inbox/messages/{id}/reply) and the new messages;
account.needs_reconnect carries the account and a reason.
Headers: Content-Type: application/json, User-Agent: PostFlow-Webhooks/1, X-PostFlow-Event, and X-PostFlow-Signature.
Delivery
Section titled “Delivery”Answer with any 2xx within 10 seconds. Other answers, timeouts and redirects count as failures and are retried with growing
delays (doubling each time, up to an hour apart), up to 5 more times. The last result shows next to the webhook in Settings.
Verifying the signature
Section titled “Verifying the signature”X-PostFlow-Signature: t=<unix seconds>,v1=<hex> where v1 is the HMAC-SHA256 of "<t>.<raw body>" with the webhook’s signing secret
(shown in Settings). Compare in constant time and reject old timestamps.
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verify(rawBody, header, secret, toleranceSec = 300) { const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('='))) if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSec) return false const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex') return v1?.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected))}import hashlib, hmac, time
def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) if abs(time.time() - int(parts["t"])) > tolerance: return False expected = hmac.new(secret.encode(), f'{parts["t"]}.'.encode() + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(parts.get("v1", ""), expected)function verify(string $rawBody, string $header, string $secret, int $tolerance = 300): bool { parse_str(str_replace(',', '&', $header), $p); if (abs(time() - (int) $p['t']) > $tolerance) return false; $expected = hash_hmac('sha256', $p['t'] . '.' . $rawBody, $secret); return hash_equals($expected, $p['v1'] ?? '');}Use the raw request body: parsing and re-serializing the JSON changes the bytes and breaks the signature.
Managing endpoints with the API
Section titled “Managing endpoints with the API”Owners’ keys can list, create,
update (including active: false to pause), delete and
test endpoints, and rotate the signing secret. After
rotating, deliveries are signed with the new secret immediately: have your receiver accept the old and the new secret until it’s updated.