Skip to content

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.

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.

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.

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))
}

Use the raw request body: parsing and re-serializing the JSON changes the bytes and breaks the signature.

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.