Events
Every webhook event FoPost can send, when it fires, and its payload.
Seventeen events, in five families. Subscribe to delivery.* when you want per-account granularity, post.* when you only care about the overall outcome. Subscribing to both means two notifications for the same publish, which is fine as long as your handler expects it.
Posts
| Event | Fires when |
|---|---|
post.scheduled | A post was put on the calendar, by hand, by an approval, by a bulk import or by an automation. Rescheduling fires it again |
post.published | Every account on the post published successfully |
post.partially_failed | Some accounts published, some did not |
post.failed | No account published (failed and cancelled deliveries both count) |
post.deleted | A post was deleted. Any delivery still pending was cancelled with it |
The three outcome events fire once per post, when every account has a final status. data carries the post and its status:
{ "postId": "7d2b8c11-…", "status": "published" }post.scheduled adds scheduleAt; post.deleted carries only postId.
Delivery outcomes
One event per account on the post.
| Event | Fires when |
|---|---|
delivery.published | One account published. Fires once per account |
delivery.failed | One account will not publish, after its retries, or immediately when the failure is final, such as expired credentials or an error the platform will never accept |
delivery.delayed | One delivery was held by a platform rate limit and will retry at retryAt |
delivery.published data:
{ "postId": "…", "postAccountId": "…", "accountId": "…", "platform": "linkedin", "externalUrl": "https://…" }externalUrl is the live post when the platform reports one, and null when it does not.
delivery.failed swaps externalUrl for errorCode and errorMessage; delivery.delayed carries reason: "platform_rate_limit", a message, and retryAt. Ordinary retry backoff between attempts does not emit delivery.delayed; only a platform rate limit does.
Accounts
| Event | Fires when |
|---|---|
account.connected | A social account was connected to the workspace |
account.disconnected | A social account was disconnected |
account.health_changed | A connected account's credential health changed: degraded, expired, revoked, or recovered |
account.connected and account.disconnected carry the account:
{ "accountId": "…", "platform": "instagram", "username": "yourbrand" }account.health_changed adds the transition:
{ "accountId": "…", "platform": "instagram", "username": "yourbrand", "previousStatus": "healthy", "newStatus": "expired" }This is the one worth wiring even if you skip the rest. It is how you learn a token died before a scheduled post silently fails. It fires on recovery too, so you can close the alert it opened. When the change comes from a failed token refresh rather than a health check, the payload carries a reason instead of a username.
Inbox
One event per new inbound item, as the inbox poll picks it up. Your own replies never fire one.
| Event | Fires when |
|---|---|
inbox.comment_received | Someone commented on one of your posts |
inbox.mention_received | Someone mentioned one of your accounts |
inbox.message_received | A direct message arrived |
inbox.review_received | A review or Page recommendation arrived |
{ "itemId": "…", "type": "comment", "platform": "instagram", "accountId": "…", "receivedAt": "2026-09-01T09:00:12.000Z" }The payload is ids only. Fetch the item from the inbox for its text and author, so nothing a customer wrote sits in your webhook logs. Building a bot on inbox.message_received? The chat adapter does the verify-and-fetch half for you.
Automations
| Event | Fires when |
|---|---|
automation.triggered | A run started |
automation.completed | A run finished every step |
automation.failed | A run stopped on an error |
{ "automationId": "…", "runId": 4821, "totalSteps": 3 }automation.triggered carries automationId and runId; automation.completed adds totalSteps; automation.failed adds error.
The envelope
Every delivery is the same three-key body:
{ "event": "<name>", "data": { … }, "timestamp": "<ISO 8601>" }timestamp is when the event was queued. The delivery id lives in the X-FoPost-Delivery header. Deduplicate on that, since a retried delivery repeats the identical body. Ids in data are the same public ids the API uses, so a webhook payload can be followed with a GET.
Next
Related documentation
- Webhooks
Hear about publishing outcomes and account health without polling.
- Verifying Signatures
Prove a delivery came from FoPost before acting on it.
- Retries and Failures
Timeouts, retry backoff, auto-disable, and test deliveries.
- Inbox
Comments, mentions and reviews from every connected account, in one list.
- Chat Adapter
Plug the FoPost inbox into a chatbot framework as one send/receive channel.
- API Overview
Base URL, envelopes, pagination, and errors for the FoPost REST API.