# Webhooks

> Get Xholic events (posts published, leads found, DMs, Worker drafts ready) pushed to your HTTPS endpoint, signed with Standard Webhooks.

Source: https://docs.xholic.ai/webhooks/

Webhooks push account events to your server as they happen: a post went live, a lead was found, a DM arrived, a Worker batch is ready. They fire only on data Xholic already collects, so they never cause extra X calls. Mentions and replies arrive at your plan's sync cadence; DMs arrive close to real time.

Prefer polling? The same events are in the [events feed](/webhooks/events-feed/).

## Set up an endpoint

In Xholic, open **Settings → API / MCP / CLI → Webhooks** and add an HTTPS URL. Pick the [events](/webhooks/events/) and the X accounts it should receive (all by default). The signing secret (`whsec_…`) is shown there; rotate it any time. **Send test event** delivers a `webhook.test` event.

| Plan | Trial | Pro | Max | Ultra |
| --- | --- | --- | --- | --- |
| Endpoints | 1 | 3 | 5 | 10 |

## What arrives

A `POST` with a JSON body:

```json
{
  "id": "post.published:…",
  "type": "post.published",
  "occurred_at": "2026-10-28T14:00:03.000Z",
  "account": {
    "user_id": "…",
    "handle": "yourhandle"
  },
  "data": {
    "post_id": "…",
    "tweet_id": "1850000000000000000",
    "tweet_url": "https://x.com/yourhandle/status/1850000000000000000",
    "text": "The first 280 characters of the post…",
    "source": "freeform",
    "posted_at": "2026-10-28T14:00:03.000Z"
  },
  "api_version": "v1"
}
```

Payloads are small: IDs plus the few fields you usually need. Fetch the rest with the API (for example `queue.get_post` or `posts.get`).

| Header | Value |
| --- | --- |
| `webhook-id` | The event id. The same on every retry: use it to drop duplicates. |
| `webhook-timestamp` | Unix seconds of this attempt. |
| `webhook-signature` | `v1,<base64 HMAC-SHA256>`; see [Verify signatures](/webhooks/verify/). |
| `content-type` | `application/json` |
| `user-agent` | `Xholic-Webhooks/1` |

The format is [Standard Webhooks](https://www.standardwebhooks.com), so any Standard Webhooks library verifies it with your `whsec_` secret.

## Delivery and retries

Reply with any `2xx` within 10 seconds. Anything else, a timeout or a network error counts as a failure and the delivery is retried:

| Try | When | Since the first try |
| --- | --- | --- |
| 1 | Right away | 0 |
| 2 | 30 seconds after try 1 | about 30 seconds |
| 3 | 2 minutes after try 2 | about 2.5 minutes |
| 4 | 10 minutes after try 3 | about 12.5 minutes |
| 5 | 30 minutes after try 4 | about 42.5 minutes |
| 6 | 1 hour after try 5 | about 1.7083333333333333 hours |
| 7 | 2 hours after try 6 | about 3.7083333333333335 hours |
| 8 | 6 hours after try 7 | about 9.708333333333334 hours |
| 9 | 12 hours after try 8 | about 21.708333333333332 hours |

- Delivery is **at least once**: the same event can arrive twice, and events can arrive out of order. Dedupe by `webhook-id` and order by `occurred_at`.
- An endpoint that keeps failing for 3 days is switched off and its owner is emailed. Turn it back on in Settings.
- Every delivery is logged for 30 days with its response status; **Replay** sends one again.
- Payloads are at most 256 KB.

## Safety

Only `https://` URLs are accepted. URLs that resolve to private, loopback or link-local addresses are refused, both when you save them and before every delivery. Respond quickly and do the work in the background; a slow endpoint gets timeouts and retries.
