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

# Webhook Alerts quickstart

> Run a receiver, send it a signed test delivery, then create a live alert for large BTC liquidations, with an agent, the dashboard, or the REST API.

This page takes you from an empty account to a signed test delivery that your receiver checked and accepted, then to a live alert for Hyperliquid BTC liquidations of \$250,000 or more. You need:

* **A Build plan or higher.** Endpoints, rules and test deliveries start on Build. On Free, those calls answer with a 400 that names the plan that includes them.
* **A public HTTPS URL that reaches your receiver.** The URL must start with `https://`, serve a valid TLS certificate, and resolve only to public IP addresses. Private, loopback, link-local and carrier-grade NAT addresses are refused when you register the endpoint and again before every delivery, and redirects are not followed. For local development, run an HTTPS tunnel that forwards a public URL to `localhost:8080`.

The examples use `https://example.com/hooks/0xarchive`; replace it with your own URL everywhere.

## 1. Prepare a receiver

Your receiver reads the raw request body, checks the `0xa-signature` header with the endpoint's signing secret, and answers with a 2xx. Both versions below use the signature helper in the 0xArchive SDKs (version `1.12.0` and later) and serve `POST /hooks/0xarchive` on port 8080.

<CodeGroup>
  ```python receiver.py theme={"theme":"github-dark"}
  import os

  from flask import Flask, request
  from oxarchive import WebhookSignatureError, WebhookVerifier

  app = Flask(__name__)
  verifier = WebhookVerifier(os.environ["OXARCHIVE_WEBHOOK_SECRET"])


  @app.post("/hooks/0xarchive")
  def receive():
      try:
          # The raw bytes, before any JSON parsing.
          event = verifier.verify(request.get_data(), request.headers)
      except WebhookSignatureError:
          return "", 400
      print("received", event.type, event.id, event.payload["data"])
      return "", 204
  ```

  ```javascript receiver.mjs theme={"theme":"github-dark"}
  import http from "node:http";
  import { verifyWebhookSignature } from "@0xarchive/sdk";

  const secret = process.env.OXARCHIVE_WEBHOOK_SECRET;
  if (!secret) throw new Error("Set OXARCHIVE_WEBHOOK_SECRET");

  http
    .createServer((req, res) => {
      if (req.method !== "POST" || req.url !== "/hooks/0xarchive") {
        res.writeHead(404).end();
        return;
      }
      const chunks = [];
      req.on("data", (chunk) => chunks.push(chunk));
      req.on("end", async () => {
        // The raw bytes, before any JSON parsing.
        const rawBody = Buffer.concat(chunks);
        const ok = await verifyWebhookSignature({ payload: rawBody, headers: req.headers, secret });
        if (!ok) {
          res.writeHead(400).end();
          return;
        }
        const event = JSON.parse(rawBody.toString("utf8"));
        console.log("received", event.type, event.id, event.data);
        res.writeHead(204).end();
      });
    })
    .listen(8080);
  ```
</CodeGroup>

Install the dependencies now. You start the receiver in step 2, once you have the endpoint's secret, with the matching command:

<CodeGroup>
  ```bash Python theme={"theme":"github-dark"}
  pip install oxarchive flask
  export OXARCHIVE_WEBHOOK_SECRET="whsec_..."
  flask --app receiver run --port 8080
  ```

  ```bash Node.js theme={"theme":"github-dark"}
  npm install @0xarchive/sdk
  export OXARCHIVE_WEBHOOK_SECRET="whsec_..."
  node receiver.mjs
  ```
</CodeGroup>

These receivers only print the event. [Signatures and payloads](/webhooks/signatures-and-payloads#a-receiver-that-does-not-lose-events) shows one that records each event before answering, and the same signature check without the SDK.

## 2. Register the endpoint and send a test

Pick the way you work. Each tab is the whole path, and each uses the receiver from [step 1](#1-prepare-a-receiver).

<Tabs>
  <Tab title="With an agent">
    Ask for the alert you want in one sentence:

    > Alert me at [https://example.com/hooks/0xarchive](https://example.com/hooks/0xarchive) when a BTC liquidation over \$250,000 happens on Hyperliquid. Show me the signing secret, wait until I say my receiver has it, then send a test delivery.

    The agent estimates how often that rule would have fired, creates the endpoint and the rule, sends a signed test delivery once your receiver is ready, and reports the delivery's state and the status code your receiver returned. That one request also covers step 4.

    <Steps>
      <Step title="Connect the MCP server">
        Add `https://mcp.0xarchive.io/mcp` as a remote HTTP MCP server and sign in with your client's built-in OAuth flow; there is no API key to paste. In Claude Code:

        ```bash theme={"theme":"github-dark"}
        claude mcp add --transport http 0xarchive \
          https://mcp.0xarchive.io/mcp
        ```

        Approve both webhook scopes on the consent screen: `mcp:webhooks.read` for the catalog, your rules, the delivery log and both previews, and `mcp:webhooks.write` for creating, testing and changing anything. If the consent screen does not offer them, the webhook tools are not enabled on the server you reached; use the dashboard or REST tab instead. [MCP server](/mcp-server) covers other clients.
      </Step>

      <Step title="Send the request and install the secret">
        The agent shows the endpoint's signing secret once. Export it as `OXARCHIVE_WEBHOOK_SECRET`, start your receiver, and tell the agent it is ready.
      </Step>

      <Step title="Or go one step at a time">
        > Create a webhook endpoint for [https://example.com/hooks/0xarchive](https://example.com/hooks/0xarchive).

        Store the secret it returns, export it, and start your receiver. Then:

        > Send that endpoint a test delivery and show me its entry in the delivery log.
      </Step>
    </Steps>
  </Tab>

  <Tab title="In the dashboard">
    <Steps>
      <Step title="Add the endpoint">
        Open **Webhooks** in the [dashboard](https://0xarchive.io/dashboard?tab=webhooks\&utm_source=docs\&utm_medium=referral\&utm_campaign=docs_referral\&utm_content=webhooks_quickstart). Choose **Add endpoint**, select **Webhook URL**, enter your URL as the **Endpoint URL**, and choose **Add endpoint**.

        The **Endpoint created** dialog shows the signing secret once. Copy it, export it as `OXARCHIVE_WEBHOOK_SECRET`, start your receiver, then choose **Done**.
      </Step>

      <Step title="Send a test">
        On the **Endpoints** tab, choose **Send test event** on your endpoint.
      </Step>

      <Step title="Find the test in the log">
        Open the **Deliveries** tab. The newest `webhook.test` row is your test, with its state and the response your receiver gave. **Raw payload** shows the exact body that was signed.
      </Step>
    </Steps>
  </Tab>

  <Tab title="With the REST API">
    <Steps>
      <Step title="Set your API key">
        Create a key in the [dashboard](https://0xarchive.io/dashboard?utm_source=docs\&utm_medium=referral\&utm_campaign=docs_referral\&utm_content=webhooks_quickstart_key) if you do not have one, and export it. The commands below also use `jq`, and the receiver from [step 1](#1-prepare-a-receiver).

        ```bash theme={"theme":"github-dark"}
        export OXARCHIVE_API_KEY="0xa_your_api_key"
        ```
      </Step>

      <Step title="Create the endpoint">
        ```bash theme={"theme":"github-dark"}
        RESPONSE=$(curl -s -X POST https://api.0xarchive.io/v1/webhooks/endpoints \
          -H "X-API-Key: $OXARCHIVE_API_KEY" \
          -H "content-type: application/json" \
          -d '{"url": "https://example.com/hooks/0xarchive", "description": "quickstart receiver"}')
        ENDPOINT_ID=$(echo "$RESPONSE" | jq -r '.data.id')
        echo "$RESPONSE" | jq -r '.data.secret'
        ```

        The last line prints the signing secret. It is returned on this response only, so export it as `OXARCHIVE_WEBHOOK_SECRET` now and start your receiver. If `ENDPOINT_ID` is `null`, print `$RESPONSE`: it carries the error, such as a URL that was refused.
      </Step>

      <Step title="Send a test">
        ```bash theme={"theme":"github-dark"}
        TEST=$(curl -s -X POST "https://api.0xarchive.io/v1/webhooks/endpoints/$ENDPOINT_ID/test" \
          -H "X-API-Key: $OXARCHIVE_API_KEY")
        DELIVERY_ID=$(echo "$TEST" | jq -r '.data.delivery_id')
        ```
      </Step>

      <Step title="Find the test in the log">
        ```bash theme={"theme":"github-dark"}
        curl -s "https://api.0xarchive.io/v1/webhooks/endpoints/$ENDPOINT_ID/deliveries?limit=20" \
          -H "X-API-Key: $OXARCHIVE_API_KEY" \
          | jq --arg id "$DELIVERY_ID" '.data[] | select(.id == $id) | {state, attempts, last_status_code, last_error}'
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

## 3. Check the test delivery

The test worked when both of these are true:

* **Your receiver accepted it.** It printed a `webhook.test` event whose `data.message` reads "Test event from 0xArchive. Your receiver and signature verification are working."
* **Its log entry agrees.** The delivery's `state` is `delivered` and `last_status_code` is the status your receiver returned.

A delivered test looks like this, as one entry of the delivery log's `data`:

```json theme={"theme":"github-dark"}
{
  "id": "8c2d5f1e-4b7a-4f0e-9d3c-2a1b6e7f8091",
  "event_id": "04bade8a-659a-4609-a183-733163bc6a22",
  "event_type": "webhook.test",
  "state": "delivered",
  "attempts": 1,
  "last_status_code": 204,
  "last_error": null,
  "last_latency_ms": 41,
  "next_attempt_at": "2026-10-05T12:01:07.530Z",
  "delivered_at": "2026-10-05T12:01:07.571Z",
  "created_at": "2026-10-05T12:01:07.512Z",
  "payload": {
    "id": "04bade8a-659a-4609-a183-733163bc6a22",
    "type": "webhook.test",
    "schema_version": 1,
    "observed_at": "2026-10-05T12:01:07.512Z",
    "data": { "message": "Test event from 0xArchive. Your receiver and signature verification are working." }
  }
}
```

The log entry's `id` is the `delivery_id` the test call returned, and `event_id` is the test's own event id. If the state is not `delivered` yet:

* **`pending` with `attempts` at 0** means the delivery has not been attempted yet. Check again in a few seconds.
* **`pending` with `attempts` above 0** means an attempt failed and another is scheduled for `next_attempt_at`. `last_status_code` and `last_error` say why. A 400 from the receivers above means the signature check refused the request; the usual cause is a body that was parsed before it was checked, or a secret other than the one this endpoint returned.

[Delivery and retries](/webhooks/delivery-and-retries#find-out-why-deliveries-stopped) walks through the other failures. A test delivery is a real signed delivery through the same dispatch path as every other event, so it counts toward the day's delivery allowance. It checks that the receiver is reachable and that its signature check works; it does not check whether a rule matches.

## 4. Create the alert you want

Now point the endpoint at something real: every Hyperliquid BTC liquidation of \$250,000 or more. Check how often it would have fired first, then create it.

<Tabs>
  <Tab title="With an agent">
    If you sent the one-sentence request in step 2, this rule already exists. Otherwise ask:

    > Estimate how often Hyperliquid BTC liquidations of \$250,000 or more would have alerted me over the last week, then create that rule on my endpoint.

    The agent calls `estimate_webhook_subscription`, then `create_webhook_subscription`, and reports the stored rule.

    <Accordion title="Every webhook tool the agent can use">
      * Read the catalog and your plan: `list_webhook_event_types`, `get_webhook_limits`.
      * Size a rule before creating it: `estimate_webhook_subscription`, `dry_run_webhook_subscription`.
      * Endpoints: `create_webhook_endpoint`, `list_webhook_endpoints`, `test_webhook_endpoint`, `enable_webhook_endpoint`, `rotate_webhook_endpoint_secret`, `delete_webhook_endpoint`.
      * Rules: `create_webhook_subscription`, `list_webhook_subscriptions`, `update_webhook_subscription`, `resume_webhook_subscription`, `delete_webhook_subscription`.
      * Watched addresses: `list_webhook_watched_addresses`, `add_webhook_watched_address`, `delete_webhook_watched_address`.
      * Deliveries: `list_webhook_deliveries`, `redeliver_webhook_delivery`.

      Each tool carries the same rules these pages describe, so an agent configures the same thing you would. Rotating a secret and deleting an endpoint cannot be undone for a live receiver, so confirm before you let an agent run them.
    </Accordion>
  </Tab>

  <Tab title="In the dashboard">
    Choose **New subscription** and pick **Market liquidation** (`market.liquidation`). Under **Markets**, select the Hyperliquid venue and add `BTC` under **Symbols**. Under **Conditions**, choose **Add condition**, pick `notional_usd`, and set it to greater than or equal to `250000`. **Estimated frequency** shows how many deliveries a day the rule would have produced. Pick your endpoint under **Delivery** and choose **Add subscription**.
  </Tab>

  <Tab title="With the REST API">
    ```bash theme={"theme":"github-dark"}
    CONFIG='{"venue": "hyperliquid", "symbols": ["BTC"], "conditions": [{"metric": "notional_usd", "op": ">=", "value": 250000}]}'

    # How often would it have fired? Typical day and busiest day over the last week.
    jq -n --argjson config "$CONFIG" '{event_type: "market.liquidation", config: $config, lookback_days: 7}' \
      | curl -s -X POST https://api.0xarchive.io/v1/webhooks/subscriptions/estimate \
          -H "X-API-Key: $OXARCHIVE_API_KEY" -H "content-type: application/json" -d @- \
      | jq '.data | {per_day_p50, per_day_max}'

    # Create the rule on your endpoint.
    jq -n --arg endpoint "$ENDPOINT_ID" --argjson config "$CONFIG" \
        '{endpoint_id: $endpoint, event_type: "market.liquidation", filters: $config}' \
      | curl -s -X POST https://api.0xarchive.io/v1/webhooks/subscriptions \
          -H "X-API-Key: $OXARCHIVE_API_KEY" -H "content-type: application/json" -d @- \
      | jq '.data | {id, status, enabled}'
    ```
  </Tab>
</Tabs>

The alert is live when the rule shows `status` `active` and `enabled` `true` in `GET /v1/webhooks/subscriptions`. The dashboard shows it as **Active**, with **Not fired yet** until the first match and **Last fired** after that. The estimate's typical day tells you how often to expect a delivery; on a quiet day there may be none. Each liquidation it matches arrives at your receiver as a signed `market.liquidation` event and appears in the delivery log next to the test.

The test needed no rule, so this alert is the only rule you created. It takes one rule from your plan's allowance, which is eight on Build.

## Next step

<Card title="Add your next alert" icon="list" href="/webhooks/events" horizontal>
  Pick another event from the catalog: funding flips, open interest moves, oracle jumps, or the activity of a wallet you follow.
</Card>


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