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

# Webhooks

> Receive X activity in real time — DMs, mentions, replies, likes and follows POSTed to your server, typed and signed.

Instead of polling for new activity, register a URL you control and OmniX **POSTs
events to it** the moment they happen on the connected X account — direct messages,
plus mentions, replies, likes and follows.

1. **Register** your URL with `POST /webhooks`.
2. Confirm ownership with a one-time **CRC handshake**.
3. Receive **typed, signed events** at your URL as activity happens.

## What you provide

<CardGroup cols={3}>
  <Card title="auth_token" icon="key">
    The X account whose activity you want delivered.
  </Card>

  <Card title="encryption_code" icon="lock">
    Supply it to receive **DM** events. Omit it and you still get tweet activity
    (mentions/replies/likes/follows).
  </Card>

  <Card title="secret" icon="shield">
    Returned once on create (or supply your own). Used to answer CRC and to verify the
    signature on every delivery.
  </Card>
</CardGroup>

<Note>
  `POST /webhooks` (create) also accepts an optional **`proxy`**. It is stored with the
  webhook, and the background poller routes all of that account's X traffic through it for
  the lifetime of the webhook. See [Authentication](/authentication#routing-through-a-proxy-optional).
</Note>

## 1. CRC — proving you own the URL

Before any events are delivered, your URL receives a `GET` with a `crc_token` query param.
Your endpoint must reply within 10s with:

```json theme={null}
{ "response_token": "sha256=<base64( HMAC-SHA256(secret, crc_token) )>" }
```

A webhook only becomes `valid:true` and starts receiving events once it passes CRC.

<Warning>
  The `secret` is generated by us and returned **once** in the Create response — so the
  *first* create can’t pass CRC until your receiver knows it. Either **supply your own
  `secret`** in the Create body (so CRC passes immediately), or create first, configure
  your receiver with the returned secret, then call **Validate**.
</Warning>

## 2. Verifying each delivery

Every event is `POST`ed with this header:

```
x-twitter-webhooks-signature: sha256=<base64( HMAC-SHA256(secret, rawRequestBody) )>
```

Recompute it over the raw body with your `secret` and compare — if it matches, the event
genuinely came from OmniX.

<Tip>
  A minimal Node receiver (CRC + signature check) is only a few lines — see the
  **Quickstart** snippet below.
</Tip>

## 3. The events you receive

Each delivery is one JSON object with a `type`.

| `type`                                              | When                          |
| --------------------------------------------------- | ----------------------------- |
| `message.received` / `message.sent`                 | a DM is received / sent       |
| `message.edited`                                    | a DM is edited                |
| `message.deleted`                                   | a DM is deleted               |
| `message.reactionAdded` / `message.reactionRemoved` | a reaction changes on a DM    |
| `tweet.mention`                                     | someone @mentions the account |
| `tweet.reply`                                       | someone replies to its tweet  |
| `tweet.quote`                                       | someone quote-tweets it       |
| `tweet.like`                                        | someone likes its tweet       |
| `tweet.retweet`                                     | someone retweets it           |
| `user.follow`                                       | someone follows it            |

<Info>
  Tweet events fire on **incoming** activity (someone acts on your account). Your own
  outgoing actions — e.g. following someone yourself — do not produce an event.
</Info>

### Example payloads

```json title="message.received" theme={null}
{
  "type": "message.received",
  "conversation_id": "1000000000000000001:2000000000000000002",
  "message_id": "a1b2c3d4-0000-0000-0000-000000000000",
  "seq_id": "3000000000000000000",
  "sender_id": "2000000000000000002",
  "from_me": false,
  "text": "hey there",
  "created_at": "2026-06-14T12:24:52.899000Z"
}
```

```json title="tweet.mention" theme={null}
{
  "type": "tweet.mention",
  "tweet_id": "4000000000000000000",
  "text": "@your_handle check this out",
  "author_id": "5000000000000000000",
  "author_screen_name": "someone",
  "author_name": "Someone"
}
```

```json title="user.follow" theme={null}
{
  "type": "user.follow",
  "actor_ids": ["5000000000000000000"],
  "actor_screen_names": ["someone"]
}
```

## Typical flow

<Steps>
  <Step title="Create">
    `POST /webhooks` with your `url`, `auth_token`, and (for DMs) `encryption_code`.
    Save the returned `secret`. Supply your own `secret` to pass CRC on this call.
  </Step>

  <Step title="Validate (if needed)">
    If you didn’t pre-share the secret, configure your receiver with the returned secret
    and call `PUT /webhooks/{id}` to re-run CRC until `valid:true`.
  </Step>

  <Step title="Receive">
    Handle `POST`s at your URL — verify `x-twitter-webhooks-signature`, then `switch` on
    `type`. Respond `200` within 10s.
  </Step>

  <Step title="Recover (optional)">
    Missed deliveries? `POST /webhooks/replay` re-sends the last 24h of events.
  </Step>

  <Step title="Stop">
    `DELETE /webhooks/{id}` to stop delivery.
  </Step>
</Steps>

## Pricing

Management calls are billed per call: **$0.0025** for create / list / validate / delete, **$0.005** for replay. (Event deliveries themselves aren’t charged per event.)

<Tip>
  Try it from the **API Reference → Webhooks** pages. For local testing you can register a
  `http://localhost` URL (no public tunnel needed).
</Tip>
