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

# Webhooks

> Receive signed events when posts are scheduled, published, fail, or are deleted.

PostlyBee sends one HTTPS `POST` per event to each matching subscription. Events are signed with the [Standard Webhooks](https://www.standardwebhooks.com/) scheme, retried for about 24 hours, and delivered at least once.

## Events

| Type | When | `data` |
| - | - | - |
| `post.scheduled` | An approved post is queued, rescheduled, or edited. | `post` |
| `post.unscheduled` | A queued post went back to draft or to awaiting approval. | `post` |
| `post.publishing` | Publishing to the provider starts. | `post` |
| `post.published` | The provider accepted the post. | `post` with `releaseURL` and `releaseId` |
| `post.failed` | The post could not be published. | `post`, `error` |
| `post.deleted` | The post was deleted. | `post`, `deletedBy` |
| `integration.disconnected` | An account must be reconnected, or was disabled or deleted. | `integration`, `reason` |
| `integration.reconnected` | An account that needed reconnecting works again. | `integration` |
| `webhook.test` | You sent a test event. | `message` |

Drafts do not emit events. `post` never contains the post content:

```json theme={null}
{
  "id": "evt_6a1d4f5b-8c3e-4d2a-9b8c-0e1f2d3c4b5a",
  "type": "post.failed",
  "createdAt": "2026-10-03T09:00:12.000Z",
  "workspaceId": "4f0c…",
  "data": {
    "post": {
      "id": "clz8v2f6r0001k9p8t5m3w7q1",
      "externalId": "order-123",
      "group": "f2a1d1f4-…",
      "state": "ERROR",
      "publishDate": "2026-10-03T09:00:00.000Z",
      "integration": { "id": "clz8tq4…", "providerIdentifier": "linkedin", "name": "Acme" },
      "releaseURL": null,
      "releaseId": null,
      "error": "The channel needs to be reconnected."
    },
    "error": {
      "code": "integration_disconnected",
      "message": "The channel needs to be reconnected.",
      "retryable": false
    }
  }
}
```

`error.code` is one of `quota_exceeded`, `integration_disconnected`, `content_rejected`, `media_invalid`, `rate_limited`, `provider_error`, or `unknown`. `unknown` is never retryable: the provider may have accepted the post, and retrying could publish it twice.

## Subscriptions

Create subscriptions in **Settings → Webhooks** or with the API (`webhooks:write` scope). The secret is returned only once:

```bash theme={null}
curl -X POST "https://api.postlybee.com/public/v1/workspaces/$WORKSPACE_ID/webhooks" \
  -H "Authorization: Bearer $POSTLYBEE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"My app","url":"https://example.com/webhooks/postlybee","events":["post.published","post.failed"]}'
```

Omit `events` to receive every type, and omit `integrationIds` to cover every account. Endpoints must be public HTTPS URLs.

## Verifying signatures

Each request carries three headers:

| Header | Value |
| - | - |
| `webhook-id` | Event ID. Stays the same across retries; use it to de-duplicate. |
| `webhook-timestamp` | Unix seconds when this attempt was sent. |
| `webhook-signature` | `v1,<base64 HMAC-SHA256>` of `id.timestamp.body`; space-separated when several apply. |

Verify with the raw body, reject timestamps more than five minutes old, and de-duplicate by `webhook-id`. With the Node SDK (works in Node 18+, Cloudflare Workers, and Deno):

```typescript theme={null}
import { verifyWebhook } from 'postlybee';

const body = await request.text();
const event = await verifyWebhook(process.env.POSTLYBEE_WEBHOOK_SECRET!, request.headers, body);
```

Any Standard Webhooks library works too. During a secret rotation, deliveries carry signatures from both the old and the new secret for 24 hours.

## Delivery and retries

* A `2xx` response within 10 seconds counts as delivered. Redirects are not followed.
* Other responses and timeouts are retried after 30 s, 2 min, 10 min, 30 min, 1 h, 3 h, 6 h, and 12 h. After the eighth failed attempt the delivery is marked failed.
* After 50 failed deliveries in a row, or 72 hours without a success while deliveries fail, the subscription is paused and workspace admins are notified. Re-enable it in Settings once the endpoint is fixed.
* Pause or resume a subscription with `PUT /webhooks/{id}/enabled` or in Settings. While a subscription is paused, its events are held. Re-enabling it delivers what was held during the last 7 days, in order; older held events expire.
* Events about the same post (or the same account) are delivered in order: a later event waits until the earlier one is delivered or has failed every retry. Events about different posts may arrive in any order.
* Recent deliveries are listed in Settings and at `GET /webhooks/{id}/deliveries`. Use `GET /posts/lookup` to reconcile anything you missed.


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