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

# Webhook Alerts integration tutorial

> Build a working DEPEG_TIER_CHANGE webhook integration end to end: stand up a receiver, verify the HMAC signature, create a subscription, and test it.

A hands-on walkthrough to receive real-time depeg alerts. By the end you'll have a running endpoint that receives signed `DEPEG_TIER_CHANGE` events and verifies them. For the full API and event reference, see [Webhooks](/essentials/webhooks).

## What you'll build

This is the foundation for any real-time depeg workflow — treasury alerting, automated de-risking, or a live compliance dashboard. You'll build a small HTTP server that:

1. Receives webhook POSTs from Webacy,
2. Verifies the `X-Webhook-Signature` (so you only trust real events),
3. Reacts to the tier change.

<Steps>
  <Step title="Get your API key">
    You use your standard Webacy API key (`x-api-key`). If you don't have one, contact [info@webacy.com](mailto:info@webacy.com). No webhook-specific setup is needed.
  </Step>

  <Step title="Build a receiver that verifies signatures">
    Each delivery is signed with HMAC-SHA256 over the JSON-serialized `event` object, keyed by your subscription's `secret_key`, and sent in the `X-Webhook-Signature` header. Always verify before trusting a payload.

    <CodeGroup>
      ```javascript Node.js (Express) theme={null}
      const express = require("express");
      const crypto = require("crypto");

      const app = express();
      const SECRET = process.env.WEBACY_WEBHOOK_SECRET; // the secret_key from step 3

      function isValid(body) {
        const expected = crypto
          .createHmac("sha256", SECRET)
          .update(JSON.stringify(body.event)) // compact JSON of the event object
          .digest("hex");
        return crypto.timingSafeEqual(
          Buffer.from(expected),
          Buffer.from(body.signature),
        );
      }

      app.post("/webhooks/webacy", express.json(), (req, res) => {
        if (!isValid(req.body)) return res.status(401).end();

        const { data } = req.body.event;
        console.log(
          `${data.symbol} on ${data.chain}: ${data.old_tier} -> ${data.new_tier} (${data.deviation_pct}% off peg)`,
        );
        // ...trigger your alerting / hedging logic here...

        res.status(200).end(); // any 2xx acknowledges; non-2xx triggers retries
      });

      app.listen(3000);
      ```

      ```python Python (Flask) theme={null}
      import os, hmac, hashlib, json
      from flask import Flask, request

      app = Flask(__name__)
      SECRET = os.environ["WEBACY_WEBHOOK_SECRET"]  # the secret_key from step 3

      def is_valid(body: dict) -> bool:
          expected = hmac.new(
              SECRET.encode(),
              json.dumps(body["event"], separators=(",", ":"), ensure_ascii=False).encode(),
              hashlib.sha256,
          ).hexdigest()
          return hmac.compare_digest(expected, body["signature"])

      @app.post("/webhooks/webacy")
      def webacy_webhook():
          body = request.get_json()
          if not is_valid(body):
              return "", 401
          d = body["event"]["data"]
          print(f"{d['symbol']} on {d['chain']}: {d['old_tier']} -> {d['new_tier']} ({d['deviation_pct']}% off peg)")
          # ...trigger your alerting / hedging logic here...
          return "", 200  # any 2xx acknowledges; non-2xx triggers retries
      ```
    </CodeGroup>

    Deploy this to a public **HTTPS** URL (private/loopback hosts are rejected). For local testing, a tunnel like ngrok or a [webhook.site](https://webhook.site) bin works.
  </Step>

  <Step title="Create a subscription">
    Point a subscription at your URL and pick filters. Save the `secret_key` from the response — it's only returned once.

    ```bash theme={null}
    curl -X POST https://api.webacy.com/webhooks/subscriptions \
      -H "x-api-key: $WEBACY_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "webhookUrl": "https://your-app.com/webhooks/webacy",
        "eventTypes": ["DEPEG_TIER_CHANGE"],
        "filters": { "tiers": ["warning", "critical"], "minDeviationPct": 1.0 }
      }'
    ```

    Response (note `id` and `secret_key`):

    ```json theme={null}
    {
      "id": "42",
      "webhook_url": "https://your-app.com/webhooks/webacy",
      "event_types": ["DEPEG_TIER_CHANGE"],
      "filters": { "tiers": ["warning", "critical"], "minDeviationPct": 1.0 },
      "secret_key": "store-this-now",
      "is_active": true,
      "created_at": "2026-06-22T18:00:00.000Z"
    }
    ```

    Set `WEBACY_WEBHOOK_SECRET` in your receiver to that `secret_key`.
  </Step>

  <Step title="Send a test event">
    Trigger a synthetic delivery so you don't have to wait for a real tier change:

    ```bash theme={null}
    curl -X POST https://api.webacy.com/webhooks/subscriptions/42/test \
      -H "x-api-key: $WEBACY_API_KEY"
    ```

    Your endpoint should receive a `DEPEG_TIER_CHANGE` POST and log it. If the signature check passes, you're done — that's the same path real events take.
  </Step>

  <Step title="Go live">
    * **Filters**: tune `tiers`, `chains`, `tokens`, `minDeviationPct` to only get the alerts you care about.
    * **Retries**: failed deliveries retry up to 5 times with backoff. Inspect history with `GET /webhooks/deliveries` and re-send with `POST /webhooks/deliveries/{id}/retry`.
    * **Secret rotation**: rotate with `POST /webhooks/subscriptions/{id}/rotate-secret`. The previous secret stays valid for 24h (sent as `X-Webhook-Previous-Signature`) so you can roll without downtime.
    * **Billing**: 1 CU per management call and 1 CU per delivery.
  </Step>
</Steps>

## The event you receive

```json theme={null}
{
  "event": {
    "event_type": "DEPEG_TIER_CHANGE",
    "event_id": "1a0d2b92-7142-44de-8a7c-7faa57c96df3",
    "timestamp": "2026-06-22T18:01:30.702Z",
    "data": {
      "token_address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
      "chain": "eth",
      "symbol": "USDT",
      "old_tier": "ok",
      "new_tier": "warning",
      "risk_score": 42.5,
      "deviation_pct": 1.8,
      "price_usd": "0.982",
      "peg_usd": "1.000"
    }
  },
  "signature": "<hmac-sha256-hex>",
  "delivered_at": "2026-06-22T18:01:30.702Z"
}
```

Headers: `X-Event-Type: DEPEG_TIER_CHANGE`, `X-Webhook-Signature`, `X-Event-ID` (use for idempotency).

<Tip>
  Stuck on signatures? The string you sign is the **compact JSON of the `event` object only** (not the full body, no extra whitespace). The Node and Python snippets above do exactly that.
</Tip>
