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

# Twitter webhooks & monitor notifications API

> Deliver account and keyword monitor events to HTTPS endpoints with signed payloads, event filters, retry schedules, and HMAC verification. See event fields.

<blockquote className="agent-llms-directive">
  For the complete documentation index, see <a href="/llms.txt">llms.txt</a>.
</blockquote>

Account and keyword monitors check every second. Webhooks deliver matched events
to your server. Every delivery uses an HMAC-SHA256 signature.

Use webhooks when events from tracked accounts must reach your app without
polling. The 10 tweet types are `tweet.new`, `tweet.reply`, `tweet.quote`,
`tweet.retweet`, `tweet.media`, `tweet.link`, `tweet.poll`, `tweet.mention`,
`tweet.hashtag`, and `tweet.longform`. The 11 profile types are
`profile.avatar.changed`, `profile.banner.changed`, `profile.name.changed`,
`profile.username.changed`, `profile.bio.changed`, `profile.location.changed`,
`profile.url.changed`, `profile.verified.changed`, `profile.protected.changed`,
`profile.pinned_tweet.changed`, and `profile.unavailable.changed`. Keyword
monitors support tweet event types only. The setup
returns a monitor ID, webhook ID, one-time signing secret, and signed JSON
deliveries. Webhook operations are free. Active monitors cost 21 credits/hour
and include stored events plus webhook delivery.

<CardGroup cols={3}>
  <Card title="Account or keyword monitor" icon="radio">
    Output: monitor ID, username or query, and selected event types.
    Cost: 21 credits/hour while active.
  </Card>

  <Card title="Webhook endpoint" icon="webhook">
    Output: webhook ID, URL, event types, and one-time `secret`.
    Cost: free.
  </Card>

  <Card title="Signed delivery" icon="shield-check">
    Output: HTTPS POST with JSON body and HMAC headers.
    Cost: included with the active monitor.
  </Card>
</CardGroup>

## Choose the webhook source

<CardGroup cols={2}>
  <Card title="Account activity" icon="user-round">
    Create `POST /monitors` when one X account should emit selected tweet and
    profile event types. Store `monitorId`, `username`, `xUserId`, and
    `eventTypes`.
  </Card>

  <Card title="Keyword matches" icon="search">
    Create `POST /monitors/keywords` when a query should emit matching tweet
    events. Store `keywordMonitorId`, `query`, and `eventTypes`.
  </Card>

  <Card title="Receiver endpoint" icon="webhook">
    Create `POST /webhooks` after the monitor. Store the webhook `id`, URL,
    selected `eventTypes`, and one-time `secret` before sending tests.
  </Card>

  <Card title="Replay and audit" icon="rotate-ccw">
    Use `GET /events` for stored monitor events and
    `GET /webhooks/{id}/deliveries` for delivery attempts. Join on
    `streamEventId`.
  </Card>
</CardGroup>

## Quick setup

Set up webhooks in 3 steps:

<Steps>
  <Step title="Create a monitor">
    Choose an account monitor for one X account, or a keyword monitor for matching query results.

    <CodeGroup>
      ```bash Account monitor theme={null}
      curl -X POST https://xquik.com/api/v1/monitors \
        -H "x-api-key: xq_YOUR_KEY_HERE" \
        -H "Content-Type: application/json" \
        -d '{
          "username": "elonmusk",
          "eventTypes": ["tweet.new", "tweet.reply"]
        }' | jq
      ```

      ```bash Keyword monitor theme={null}
      curl -X POST https://xquik.com/api/v1/monitors/keywords \
        -H "x-api-key: xq_YOUR_KEY_HERE" \
        -H "Content-Type: application/json" \
        -d '{
          "query": "xquik launch",
          "eventTypes": ["tweet.new", "tweet.reply"]
        }' | jq
      ```
    </CodeGroup>

    Store the returned monitor `id`. Account events include `username`. Keyword events include `query`.
  </Step>

  <Step title="Register a webhook">
    Provide an HTTPS URL and select which event types to receive. Xquik generates a signing secret. Store it in a secret manager. The API returns it only once.

    ```bash theme={null}
    curl -X POST https://xquik.com/api/v1/webhooks \
      -H "x-api-key: xq_YOUR_KEY_HERE" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://your-server.com/webhook",
        "eventTypes": ["tweet.new", "tweet.reply"]
      }' | jq
    ```

    **Response.**

    ```json theme={null}
    {
      "id": "15",
      "url": "https://your-server.com/webhook",
      "eventTypes": ["tweet.new", "tweet.reply"],
      "secret": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
      "createdAt": "2026-02-24T10:30:00.000Z"
    }
    ```
  </Step>

  <Step title="Verify signatures">
    When events arrive, verify the `X-Xquik-Signature` header using your webhook secret to confirm authenticity. See [Signature Verification](/webhooks/verification) for implementation details.

    Send a test payload before connecting production logic:

    ```bash theme={null}
    curl -X POST https://xquik.com/api/v1/webhooks/15/test \
      -H "x-api-key: xq_YOUR_KEY_HERE" | jq
    ```
  </Step>
</Steps>

## How it works

```text theme={null}
Account or keyword monitor event -> Xquik -> Your Webhook Endpoint
```

## Delivery format

Xquik sends webhook events as HTTPS POST requests. It sends each monitor event
as its own payload. If one active monitor check finds multiple new matching
tweets, expect multiple POST requests, one per matched tweet event, instead of
one batched payload.

### Headers

<CardGroup cols={2}>
  <Card title="Content-Type" icon="braces">
    `application/json`. Payloads are always JSON.
  </Card>

  <Card title="User-Agent" icon="id-card">
    `xquik-webhooks/1.0 (+https://xquik.com)`. Identifies Xquik traffic.
  </Card>

  <Card title="X-Xquik-Timestamp" icon="clock">
    Unix epoch milliseconds. Used in the signing string and for replay window enforcement.
  </Card>

  <Card title="X-Xquik-Nonce" icon="fingerprint">
    16 random bytes in hex. Reject duplicates within the replay window.
  </Card>

  <Card title="X-Xquik-Signature" icon="shield-check">
    `sha256=HMAC_HEX_DIGEST`. HMAC-SHA256 of `<timestamp>.<nonce>.<rawBody>`.
  </Card>
</CardGroup>

### Payload body

```json theme={null}
{
  "eventType": "tweet.new",
  "schemaVersion": 1,
  "deliveryId": "502",
  "streamEventId": "9001",
  "occurredAt": "2026-02-24T14:22:00.000Z",
  "monitorId": "10",
  "monitorType": "account",
  "username": "elonmusk",
  "xUserId": "44196397",
  "data": {
    "id": "1893456789012345678",
    "text": "The future is now.",
    "author": {
      "id": "44196397",
      "username": "elonmusk",
      "name": "Elon Musk"
    },
    "isRetweet": false,
    "isReply": false,
    "isQuote": false,
    "createdAt": "2026-02-24T14:22:00.000Z"
  }
}
```

<CardGroup cols={2}>
  <Card title="eventType" icon="radio">
    Type `string`. Event type selected by the source monitor and webhook.
  </Card>

  <Card title="schemaVersion" icon="badge-check">
    Type `number`. Webhook payload schema version. Current value is `1`.
  </Card>

  <Card title="deliveryId" icon="truck">
    Type `string`. Webhook delivery ID. Every retry reuses it. Store it as the
    delivery-level idempotency key and use it for delivery-log correlation.
  </Card>

  <Card title="streamEventId" icon="link">
    Type `string`. Stored monitor event ID. Store it as the event-level
    deduplication key when you must process one monitor event once across webhook
    retries or endpoint changes.
  </Card>

  <Card title="occurredAt" icon="clock">
    Type `string`. ISO timestamp for when the event occurred.
  </Card>

  <Card title="monitorId" icon="hash">
    Type `string`. ID of the monitor that produced the event. Account monitors and keyword monitors number separately, so read it together with `monitorType`. Use both to route events when many monitors share 1 webhook URL.
  </Card>

  <Card title="monitorName" icon="tag">
    Type `string`. Your monitor label when Xquik created the event. Present when the monitor had a `name` then. A rename leaves the event unchanged.
  </Card>

  <Card title="monitorType" icon="layers">
    Type `string`. `account` or `keyword`. Matches `monitorType` on the events API.
  </Card>

  <Card title="username" icon="user">
    Type `string`. Current X username for account monitor events. It follows a username change. Omitted for keyword-only monitor events and `webhook.test`.
  </Card>

  <Card title="xUserId" icon="fingerprint">
    Type `string`. Permanent X user ID of the monitored account, on account monitor events. It stays equal when the account changes its username, so use it to match events from before and after a change.
  </Card>

  <Card title="query" icon="search">
    Type `string`. Keyword query that matched the event. Present for keyword monitor events.
  </Card>

  <Card title="data" icon="braces">
    Type `object`. Raw event object for the monitored tweet activity.
  </Card>
</CardGroup>

<Note>
  Post fields match REST responses. Profiles use `username` and `verified` for the displayed badge.
  Legacy `userName` remains available. `rawVerified` preserves the original X flag.
  The `verified` field now includes blue verification. Card, reaction, quote & repost profiles use these fields.
  Stored posts gain these fields when their source fields are available. Incomplete legacy posts remain readable.
</Note>

### Receiver storage row

After signature verification succeeds, store a compact receiver row before handing the event to workers. Use `deliveryId` for delivery-level retries and `streamEventId` for event-level processing. Do not store endpoint signing values, the raw request body, the raw signature, or full headers in shared incident rows.

```json theme={null}
{
  "record_type": "webhook_receiver_event",
  "webhook_id": "15",
  "delivery_id": "502",
  "stream_event_id": "9001",
  "event_type": "tweet.new",
  "source": "account",
  "username": "elonmusk",
  "signature_verified": true,
  "nonce_cache_key": "webhook:15:nonce_hash",
  "occurred_at": "2026-02-24T14:22:00.000Z",
  "receiver_status": 202,
  "event_join": "GET /api/v1/events/9001",
  "delivery_log": "GET /api/v1/webhooks/15/deliveries"
}
```

## Tweet & profile event shapes

Each event type includes `data`. Tweet events contain public post fields and retained legacy fields.
The `webhook.test` event contains a test message and timestamp.
The examples below show the most common fields.

### tweet.new

A new original tweet posted by the monitored account.

```json theme={null}
{
  "eventType": "tweet.new",
  "username": "elonmusk",
  "data": {
    "id": "1893456789012345678",
    "text": "The future is now.",
    "author": {
      "id": "44196397",
      "username": "elonmusk",
      "name": "Elon Musk"
    },
    "isRetweet": false,
    "isReply": false,
    "isQuote": false,
    "createdAt": "2026-02-24T14:22:00.000Z"
  }
}
```

### tweet.quote

A quote tweet posted by the monitored account.

```json theme={null}
{
  "eventType": "tweet.quote",
  "username": "elonmusk",
  "data": {
    "id": "1893456789012345679",
    "text": "Interesting take on this.",
    "author": {
      "id": "44196397",
      "username": "elonmusk",
      "name": "Elon Musk"
    },
    "isRetweet": false,
    "isReply": false,
    "isQuote": true,
    "quoted_tweet": {
      "id": "1893400000000000000",
      "text": "Our latest launch was a success.",
      "author": {
        "username": "SpaceX"
      }
    },
    "createdAt": "2026-02-24T15:10:00.000Z"
  }
}
```

### tweet.reply

A reply posted by the monitored account.

```json theme={null}
{
  "eventType": "tweet.reply",
  "username": "elonmusk",
  "data": {
    "id": "1893456789012345680",
    "text": "Great question. Working on it.",
    "author": {
      "id": "44196397",
      "username": "elonmusk",
      "name": "Elon Musk"
    },
    "isRetweet": false,
    "isReply": true,
    "isQuote": false,
    "inReplyToId": "1893411111111111111",
    "createdAt": "2026-02-24T16:30:00.000Z"
  }
}
```

### tweet.retweet

A retweet posted by the monitored account.

```json theme={null}
{
  "eventType": "tweet.retweet",
  "username": "elonmusk",
  "data": {
    "id": "1893456789012345681",
    "text": "RT @xai: Exciting news today.",
    "author": {
      "id": "44196397",
      "username": "elonmusk",
      "name": "Elon Musk"
    },
    "isRetweet": true,
    "isReply": false,
    "isQuote": false,
    "createdAt": "2026-02-24T17:00:00.000Z"
  }
}
```

### webhook.test

A test payload that the [Test Webhook](/api-reference/webhooks/test) endpoint sends to verify that your endpoint is reachable.

```json theme={null}
{
  "eventType": "webhook.test",
  "data": {
    "message": "Test delivery from Xquik"
  },
  "timestamp": "2026-02-27T12:00:00.000Z"
}
```

## Delivery order

Deliveries can arrive in any order. Xquik sends up to 20 at once to 1
endpoint, and retries can come after newer events. Order events by
`occurredAt`, then `streamEventId`.

A `429`, `503` or timeout limits Xquik to 8 at once until 1 minute passes
without one.

## Retry policy

Return `2xx` within 10 seconds for every delivery, even one you skip. Any other
status, a redirect, a timeout, or a network error is a failure. `410 Gone` is a
normal failure.

Xquik retries a failed delivery until your endpoint returns `2xx`. There is no
attempt limit. Xquik never pauses or turns off your webhook.

<CardGroup cols={2}>
  <Card title="Failing endpoint" icon="activity">
    After a failed delivery, Xquik treats your endpoint as failing. It retries 1
    waiting delivery at a time to check the endpoint. This is a real delivery,
    not a `webhook.test`. Checks start 2 seconds apart and slow to 1 every 15
    minutes. New deliveries wait as `pending` until a check succeeds.
  </Card>

  <Card title="Recovery" icon="rotate-ccw">
    When a check succeeds, Xquik starts sending `pending` deliveries at once.
    A delivery your endpoint rejected can still wait up to 7 days.
    [Resume Webhook](/api-reference/webhooks/resume) sends it at once.
  </Card>

  <Card title="1 rejected delivery" icon="triangle-alert">
    If no new event arrives within 2 seconds of a failure, Xquik retries the
    rejected delivery at the next check. New events wait for that check, up to
    15 minutes. See the table below.
  </Card>

  <Card title="Quiet endpoint" icon="hourglass">
    A rejected delivery keeps failing checks while no new events arrive. After
    about 2 quiet days, the webhook shows `needs_attention`. Delivery continues.
  </Card>
</CardGroup>

<Warning>
  While a rejected delivery fails, new events can arrive up to 15 minutes late.
</Warning>

If a new event arrives within 2 seconds of each failure, a rejected delivery
waits longer:

| Failed attempts | Next retry after |
| - | - |
| 1 | 1 second |
| 2 | 2 seconds |
| 3 | 4 seconds |
| 10 | About 9 minutes |
| 15 | About 4.5 hours |
| 20 | About 6 days |
| 21 or more | 7 days |

The wait stops growing at 7 days. Retries can arrive a little later than
listed.

### Pause or delete a webhook

Only you can stop retries. Pause the webhook with
[Update Webhook](/api-reference/webhooks/update), or delete it. Xquik holds the
deliveries that were waiting. Within 1 day, they show `exhausted`.

Xquik keeps queued events until all deliveries finish.
Events expire 30 days after Xquik creates them.
[Resume Webhook](/api-reference/webhooks/resume) sends a signed test first. Then
it starts sending held deliveries at once.

A paused webhook gets no new deliveries. Use stored event pages to catch up.
Deleting a monitor removes its waiting deliveries.

### Check delivery status

[List Deliveries](/api-reference/webhooks/deliveries) shows each delivery's
`status`: `pending`, `delivered`, `failed`, or `exhausted`. `attempts` counts
the tries so far. A retry reuses the same `deliveryId`, so store it and skip
repeats.

[List Webhooks](/api-reference/webhooks/list) shows `deliveryStatus` and
`consecutiveFailures`. `needs_attention` means 200 failed checks in a row. That
takes about 2 days. Xquik keeps retrying.

### When deliveries fail

1. Fix your endpoint so it returns `2xx` within 10 seconds.
2. Call [Resume Webhook](/api-reference/webhooks/resume). It sends a signed test
   first.
3. When the test passes, Xquik starts sending waiting and rejected deliveries at
   once.

Without Resume, a rejected delivery can wait up to 7 days.

## Backfill after a receiver outage

Xquik resends failed deliveries after your receiver recovers. Use this handoff
when your systems lost work or the webhook was paused. Fix the receiver first.
Then use delivery rows and stored event pages to rebuild downstream work.

```json theme={null}
{
  "record_type": "webhook_receiver_backfill",
  "webhook_id": "15",
  "delivery_log": "GET /api/v1/webhooks/15/deliveries",
  "resume_endpoint": "POST /api/v1/webhooks/15/resume",
  "event_backfill_endpoint": "GET /api/v1/events?limit=100&cursor={nextCursor}",
  "source_filter": "monitorId for account monitors, keywordMonitorId for keyword monitors",
  "join_key": "delivery.streamEventId == event.id",
  "resume_fields": [
    "deliveryId",
    "streamEventId",
    "status",
    "attempts",
    "lastStatusCode",
    "lastError",
    "deliveryStatus",
    "consecutiveFailures",
    "failureHardCap",
    "nextCursor"
  ],
  "stop_when": "hasMore is false",
  "handoff_state": "receiver_fixed_page_events_join_deliveries"
}
```

Store `nextCursor` after every event page. Reprocess only events your receiver
never stored. Skip repeats by `streamEventId`, since Xquik also resends failed
deliveries. Events from a pause may never reach the webhook, so page them here.
Scope event pages with `monitorId` for account monitors or `keywordMonitorId`
for keyword monitors when the source is known. Omit both only for all-monitor
replay.

## Troubleshoot a delivery

Use this handoff when a receiver fails a signed test or keeps failing deliveries.

```json theme={null}
{
  "record_type": "webhook_delivery_troubleshooting",
  "webhook_id": "15",
  "signed_test": "POST /api/v1/webhooks/15/test",
  "resume_endpoint": "POST /api/v1/webhooks/15/resume",
  "delivery_log": "GET /api/v1/webhooks/15/deliveries",
  "event_join": "GET /api/v1/events/{id}",
  "event_id_source": "streamEventId",
  "route_on": [
    "deliveryStatus",
    "consecutiveFailures",
    "failureHardCap",
    "status",
    "attempts",
    "lastStatusCode",
    "lastError",
    "createdAt",
    "deliveredAt"
  ],
  "test_payload_has_ids": false,
  "production_payload_ids": ["deliveryId", "streamEventId"],
  "handoff_state": "verify_signature_resume_if_needed_check_delivery_join_event"
}
```

<CardGroup cols={2}>
  <Card title="Signed receiver test" icon="flask-conical">
    Run [Test Webhook](/api-reference/webhooks/test) after changing endpoint
    code, secrets, firewall rules, or queue routing. Treat `success: true`
    with a `2xx` `statusCode` as receiver proof.
  </Card>

  <Card title="Signature and IDs" icon="shield-check">
    Verify `X-Xquik-Signature`, `X-Xquik-Timestamp`, and `X-Xquik-Nonce` on
    the raw request body. `webhook.test` omits `deliveryId` and `streamEventId`,
    while production deliveries include both IDs.
  </Card>

  <Card title="Delivery triage" icon="activity">
    Check [List Deliveries](/api-reference/webhooks/deliveries) for `status`,
    `attempts`, `lastStatusCode`, `lastError`, `createdAt`, and
    `deliveredAt`. Act on `failed`, wait on `pending`, and ignore
    `delivered`. `exhausted` means the webhook was paused or deleted.
  </Card>

  <Card title="Needs attention" icon="rotate-ccw">
    `needs_attention` does not stop delivery. Fix the receiver, then call
    [Resume Webhook](/api-reference/webhooks/resume). It starts sending waiting
    deliveries at once.
  </Card>

  <Card title="Event context" icon="link">
    Join `streamEventId` to [Get Event](/api-reference/events/get) when the
    receiver owner needs the original monitor event, tweet fields, username, or
    keyword query that triggered the delivery.
  </Card>
</CardGroup>

## Requirements

* The endpoint must use HTTPS
* The endpoint must not resolve to a private or internal IP address (localhost, 10.x.x.x, 172.16-31.x.x, 192.168.x.x, 169.254.x.x)
* Endpoints must return `2xx` within 10 seconds

## Where to go next

<CardGroup cols={2}>
  <Card title="Webhook API reference" icon="book-open" href="/api-reference/webhooks/create">
    Create, list, update, deactivate, test, resume, and inspect webhook deliveries.
  </Card>

  <Card title="Signature verification" icon="shield-check" href="/webhooks/verification">
    Verify HMAC-SHA256 signatures and implement idempotency.
  </Card>

  <Card title="Testing webhooks" icon="flask-conical" href="/guides/twitter-webhook-testing">
    Test webhook delivery locally with tunnels and mock payloads.
  </Card>

  <Card title="MCP equivalent" icon="bot" href="/mcp/tools">
    Use `xquik.request('/api/v1/webhooks', ...)` for create, list, update, delete, and test.
  </Card>

  <Card title="Monitor setup" icon="radio" href="/api-reference/monitors/create">
    Create account monitors that emit events for webhook delivery.
  </Card>

  <Card title="Keyword monitor setup" icon="search" href="/api-reference/monitors/create-keyword">
    Create keyword monitors that emit matching tweet events for webhook delivery.
  </Card>
</CardGroup>


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