# Social webhooks

Events for the publishing lifecycle, per platform, natively authored posts and analytics sync.

## Events

### Post lifecycle

| Event | Description |
| --- | --- |
| [`post.scheduled`](/webhooks/social/post-scheduled) | The post entered the scheduled state |
| [`post.published`](/webhooks/social/post-published) | The post published on every target platform |
| [`post.partial`](/webhooks/social/post-partial) | The post published on some platforms and failed on others |
| [`post.failed`](/webhooks/social/post-failed) | The post failed on every target platform |
| [`post.cancelled`](/webhooks/social/post-cancelled) | A scheduled post was cancelled because its account disconnected |
| [`post.recycled`](/webhooks/social/post-recycled) | A recycling schedule cloned a published post |

### Per platform

| Event | Description |
| --- | --- |
| [`post.platform.published`](/webhooks/social/post-platform-published) | One platform target finished publishing |
| [`post.platform.failed`](/webhooks/social/post-platform-failed) | One platform target failed permanently |
| [`post.platform.deleted`](/webhooks/social/post-platform-deleted) | A published platform target was deleted on the platform |
| [`post.tiktok.url_resolved`](/webhooks/social/post-tiktok-url-resolved) | A published TikTok post's public URL became available |

### Native posts

| Event | Description |
| --- | --- |
| [`post.external.created`](/webhooks/social/post-external-created) | A natively authored post was detected for the first time |
| [`post.external.updated`](/webhooks/social/post-external-updated) | A tracked native post's text or media changed |
| [`post.external.deleted`](/webhooks/social/post-external-deleted) | A tracked native post was removed from the platform |
| [`post.tracking_completed`](/webhooks/social/post-tracking-completed) | A tracking window ended and its final read finished |
| [`post.authenticity_changed`](/webhooks/social/post-authenticity-changed) | A tracked post's authenticity score crossed 40 |

### Analytics

| Event | Description |
| --- | --- |
| [`analytics.synced`](/webhooks/social/analytics-synced) | An account's analytics sync completed |

## How it behaves

A post you publish emits events at three moments, in a fixed order:

<Mermaid
  chart={`flowchart LR
  A["post created, promoted, queued, or retried"] --> B(["post.scheduled"])
  B --> C["publish time"]
  C --> D(["post.platform.published"])
  C --> E(["post.platform.failed"])
  D --> F(["post.published / partial / failed"])
  E --> F
  F -.-> G(["post.tiktok.url_resolved"])
  F -.-> H(["post.platform.deleted"])`}
/>

<Accordions>
<Accordion title="`post.scheduled` fires each time a post is scheduled" id="postscheduled-fires-each-time-a-post-is-scheduled">

One post can emit `post.scheduled` more than once over its life. See the event page for what triggers it.

</Accordion>
<Accordion title="Platform events stream in as each platform finishes" id="platform-events-stream-in-as-each-platform-finishes">

`post.platform.published` or `post.platform.failed` fires per platform target as soon as that target terminates, without waiting for the slowest one. A post targeting 3 platforms emits up to 3.

</Accordion>
<Accordion title="The rollup fires once, after every platform ends" id="the-rollup-fires-once-after-every-platform-ends">

- `post.published` when all targets succeeded.
- `post.partial` when the result is mixed.
- `post.failed` when all failed.

Exactly one fires per publishing job.

</Accordion>
<Accordion title="Two events can trail the rollup" id="two-events-can-trail-the-rollup">

`post.tiktok.url_resolved` arrives minutes after the rollup. `post.platform.deleted` can trail by days, found by a background sync roughly hourly.

</Accordion>
<Accordion title="Post events echo the `metadata` you sent" id="post-events-echo-the-metadata-you-sent">

The free-form `metadata` from [Create post](/posts/create-post) comes back as `post.metadata` on every rollup event (`post.scheduled`, `post.published`, `post.partial`, `post.failed`, `post.cancelled`, `post.recycled`) and every per-platform event.

Put your own record id in it and no event needs a lookup by `post._id`. The key is omitted when absent, and `post.external.*` events never carry it.

</Accordion>
<Accordion title="Three events sit outside the pipeline" id="three-events-sit-outside-the-pipeline">

`post.cancelled` and `post.recycled` are not part of the publish sequence. The `post.external.*` family comes from the same roughly hourly background sync of natively authored posts, not from publishing.

</Accordion>
<Accordion title="The analytics event carries no metrics or cursor" id="the-payload-is-a-delta-feed-cursor">

`analytics.synced` is a trigger. Call [`GET /v1/analytics/delta`](/analytics/get-analytics-delta) with your own last `nextCursor`: one feed call replaces polling [`GET /v1/analytics`](/analytics/get-analytics) per account.

</Accordion>
<Accordion title="An empty page does not mean nothing changed" id="an-empty-page-does-not-mean-nothing-changed">

A delta read right after `analytics.synced` can return an empty page. Re-poll with the same cursor rather than advancing.

</Accordion>
<Accordion title="Volume scales with your fleet" id="volume-scales-with-your-fleet">

`analytics.synced` fires per account about hourly. Use a dedicated endpoint so an outage cannot [disable](/webhooks#delivery-retries) low-volume events like `post.published`.

</Accordion>
<Accordion title="When it does not fire" id="when-it-does-not-fire">

`analytics.synced` skips failed or recently skipped sync cycles, inactive accounts and platforms without analytics sync. See the event page.

</Accordion>
</Accordions>

## Related

- [Webhooks](/webhooks): create an endpoint, retries, signatures.
- [Post lifecycle](/guides/post-lifecycle): the `status` values these events mirror.
- [Error handling](/guides/error-handling): what `platforms[].error`, `errorCategory` and `errorSource` contain.
- [Get post](/posts/get-post): the same data on demand.
- [Inbox webhooks](/webhooks/inbox): comments on these posts.
- [Analytics delta feed](/analytics/get-analytics-delta): the delta feed read after `analytics.synced`.
- [Get analytics](/analytics/get-analytics): the baseline read.
- [Multi-tenant analytics](/multi-tenant/analytics): per-customer dashboards on top of the feed.