> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trypost.it/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive signed HTTP notifications when posts are created, scheduled, published, fail or are deleted.

A webhook sends a signed JSON `POST` to a URL you control whenever one of the post events you picked happens in the workspace. Use it to sync a CRM, trigger an internal job, or keep another system in step with TryPost.

Only the account owner and workspace admins can manage webhooks. Find them in **Settings → Webhooks**.

## Create a webhook

<Steps>
  <Step title="Create webhook">
    In **Settings → Webhooks**, click **Create webhook**.
  </Step>

  <Step title="Endpoint URL">
    Enter a public `http://` or `https://` URL (up to 255 characters). Private, loopback and link-local addresses are rejected with **This endpoint is not allowed.**
  </Step>

  <Step title="Events">
    Select at least one event. There is no wildcard.
  </Step>

  <Step title="Copy the signing secret">
    The webhook's page opens. Copy the **Signing secret** to verify deliveries. You can reveal and copy it again at any time, or rotate it.
  </Step>
</Steps>

A new webhook starts **Enabled**. Creating or editing a webhook does not send a request; use **Send test event** for that. There is no limit on the number of webhooks.

## Events

| Event | On-screen name | When it fires | `data` |
| - | - | - | - |
| `post.created` | Post created | A new post is created | [Post payload](#post-payload) |
| `post.scheduled` | Post scheduled | An existing post changes to scheduled | Post payload |
| `post.unscheduled` | Post unscheduled | A scheduled post goes back to drafts, or back to pending approval | Post payload |
| `post.published` | Post published | A post published on all its channels | Post payload |
| `post.partially_published` | Post partially published | Some channels published and others failed | Post payload |
| `post.failed` | Post failed | A post failed to publish | Post payload |
| `post.deleted` | Post deleted | A post is deleted | `{ "id", "workspace_id" }` only |

Status events fire when the status changes, not on every save. A post created already scheduled or queued sends `post.created`, with `status` set to `scheduled`. Edits that keep the status (text, media, labels, a new time on a scheduled post) send nothing.

There is no event while a post is publishing, and none for notes, approvals, channel changes or member changes. Posts deleted because their [channel was disconnected](/knowledge-base/channels/disconnecting) or their [workspace was deleted](/knowledge-base/settings/workspaces#delete-a-workspace) do not send `post.deleted`.

## Receive a delivery

Each delivery is a `POST` with a JSON body. Respond with any `2xx` status as soon as you have accepted it. Redirects are not followed: a `3xx` counts as a failure. TryPost waits up to 10 seconds for a response.

### Headers

| Header | Value |
| - | - |
| `Content-Type` | `application/json` |
| `X-Webhook-Signature` | Hex HMAC-SHA256 of the raw request body, keyed with the signing secret |
| `User-Agent` | `TryPost.it/1.0 (+https://trypost.it)` |

### Envelope

Every delivery, including the test event, uses the same envelope:

```json theme={null}
{
  "id": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
  "type": "post.published",
  "data": {},
  "created_at": "2026-10-08T15:04:05+00:00"
}
```

| Field | Description |
| - | - |
| `id` | Delivery id. Retries of the same delivery keep it; a [replay](#deliveries-and-replay) gets a new one. Use it to deduplicate. |
| `type` | The event, or `webhook.test` for a test event. |
| `data` | The event payload. An empty object for `webhook.test`. |
| `created_at` | ISO 8601 time of this attempt. It changes on each retry, and so does the signature. |

### Verify the signature

Compute HMAC-SHA256 of the **raw body bytes** with the signing secret and compare the hex digest to `X-Webhook-Signature` with a constant-time comparison. Do not re-serialize the parsed JSON.

<CodeGroup>
  ```javascript Node.js theme={null}
  import crypto from 'node:crypto';

  const expected = crypto
    .createHmac('sha256', process.env.TRYPOST_WEBHOOK_SECRET)
    .update(rawBody)
    .digest('hex');

  const received = Buffer.from(req.headers['x-webhook-signature'] ?? '', 'utf8');
  const expectedBuf = Buffer.from(expected, 'utf8');
  const valid = received.length === expectedBuf.length
    && crypto.timingSafeEqual(expectedBuf, received);
  ```

  ```php PHP theme={null}
  $expected = hash_hmac('sha256', $rawBody, $secret);
  $valid = hash_equals($expected, (string) $request->header('X-Webhook-Signature'));
  ```

  ```python Python theme={null}
  import hmac
  import hashlib

  expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
  valid = hmac.compare_digest(expected, signature_header)
  ```
</CodeGroup>

<Warning>
  Frameworks that parse JSON before your code runs often discard the raw body. Read the unparsed body first (for example Express `express.raw({ type: 'application/json' })`), verify it, then parse it.
</Warning>

## Post payload

For every event except `post.deleted`, `data` is the post at the moment of the event:

| Field | Description |
| - | - |
| `id` | Post id |
| `workspace_id` | Workspace id |
| `user_id` | Author's user id, or `null` |
| `status` | `draft`, `pending_approval`, `scheduled`, `publishing`, `published`, `partially_published` or `failed` |
| `created_via` | `web`, `api`, `mcp`, `repurpose`, or `null` |
| `content` | Post text. May contain HTML from the editor, or be empty |
| `scheduled_at`, `published_at`, `created_at`, `updated_at` | ISO 8601, or `null` |
| `author` | `{ id, name }`, or `null` |
| `workspace` | `{ id, name }` |
| `labels` | `{ id, name, color }[]` |
| `media` | See [Media](#media) |
| `platforms` | See [Platforms](#platforms) |

### Media

| Field | Description |
| - | - |
| `id` | Media id |
| `path` | Stored path, for example `medias/{uuid}.jpg` |
| `url` | Public file URL |
| `type` | `image`, `video`, `document`, or `null` |
| `mime_type` | MIME type |
| `original_filename` | File name as uploaded or imported |
| `source` | `ai`, `unsplash`, `google_drive`, `google_photos`, `canva`, or `null` for a direct upload |
| `source_meta` | Extra data from the source, or `null` |
| `meta` | File metadata, such as `alt_text` and dimensions |

### Platforms

One entry per channel of the post:

| Field | Description |
| - | - |
| `id` | Id of the post's entry for that channel |
| `social_account_id` | Channel id |
| `platform` | For example `linkedin`, `x`, `instagram-facebook` |
| `content_type` | For example `linkedin_post`, `instagram_reel` |
| `enabled` | Whether this channel is part of the publish |
| `status` | `pending`, `publishing`, `retrying`, `pending_review`, `published`, `failed` or `rejected` |
| `platform_post_id` | Id on the network, or `null` |
| `platform_url` | Link to the live post, or `null` |
| `published_at` | ISO 8601, or `null` |
| `error_message`, `error_context` | Details when that channel failed |
| `display_name`, `display_username`, `display_avatar` | The channel as it looked when the post was saved |
| `meta` | Per-network settings of the post (see [Posting](/api-reference/guides/posting)) |
| `social_account` | `{ id, platform, display_name, username, status }`, or `null`. Never includes tokens |

## Test event

From the webhook's page, open the actions menu and click **Send test event**. TryPost sends a signed `webhook.test` delivery and waits up to 5 seconds. Your endpoint must answer with `2xx`; otherwise the test fails with the reason (**The endpoint is not reachable.** or **The endpoint returned HTTP 500.**).

A test works even when the webhook is disabled or paused. It is not added to **Deliveries** and does not affect the webhook's status.

## Status, retries and pause

| Status | Meaning |
| - | - |
| **Enabled** | Subscribed events are delivered. |
| **Disabled** | You turned it off with **Disable endpoint**. Events are skipped. |
| **Paused** | TryPost paused it after 5 failed deliveries in a row. Events are skipped. |

A failed delivery is tried 3 times, 60 seconds apart. When all three attempts fail, the delivery counts as failed; a successful delivery resets the count. At 5 failed deliveries in a row the webhook becomes **Paused** and the account owner receives an email with a link to it. That email cannot be turned off in [Notifications](/knowledge-base/settings/notifications).

Fix the endpoint, then click **Enable endpoint** to resume. Events that happened while the webhook was disabled or paused are not sent later.

## Deliveries and replay

The webhook's page lists its **Deliveries**, newest first, with the event, **HTTP status**, **Attempts**, the **Response** your endpoint returned (first 2,000 characters) and the **Message payload**. New deliveries appear live.

**Replay** sends a delivery again with the same `data`, a new envelope `id` and a new row in **Deliveries**. Replays work even when the webhook is disabled or paused, but a successful replay does not re-enable it.

Deliveries are kept for 7 days.

## Manage a webhook

From the webhook's actions menu:

* **Edit endpoint** — change the URL and events.
* **Enable endpoint** / **Disable endpoint**.
* **Rotate signing secret** — creates a new secret. The current one stops working immediately, including for retries already waiting, so update your endpoint first.
* **Send test event**.
* **Delete** — after a confirmation, deliveries stop and the webhook's history is removed.

<Note>
  Self-hosting TryPost? Deliveries run on the `webhooks` queue in Horizon, and the live delivery list needs Reverb. Set `TRYPOST_USER_AGENT` to change the `User-Agent`, and `TRYPOST_ALLOW_PRIVATE_NETWORK=true` to allow private endpoints. See [Configuration](/self-hosting/configuration#advanced) and [Production](/self-hosting/production).
</Note>

## Via the API and MCP

| Task | REST | MCP |
| - | - | - |
| List | [List webhooks](/api-reference/webhooks/list-webhooks) | `list-webhooks-tool` |
| Create | [Create webhook](/api-reference/webhooks/create-webhook) | `create-webhook-tool` |
| Get, with the secret | [Get webhook](/api-reference/webhooks/get-webhook) | `get-webhook-tool` |
| Update | [Update webhook](/api-reference/webhooks/update-webhook) | `update-webhook-tool` |
| Delete | [Delete webhook](/api-reference/webhooks/delete-webhook) | `delete-webhook-tool` |
| Send a test | [Send webhook test](/api-reference/webhooks/send-webhook-test) | `send-webhook-test-tool` |
| Rotate the secret | [Rotate webhook secret](/api-reference/webhooks/rotate-webhook-secret) | `rotate-webhook-secret-tool` |
| List deliveries | [List webhook logs](/api-reference/webhooks/list-webhook-logs) | `list-webhook-logs-tool` |
| Replay a delivery | [Replay webhook log](/api-reference/webhooks/replay-webhook-log) | `replay-webhook-log-tool` |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.