Webhooks

Step by step: create a subscription, store the secrets, verify X-Rarebl-Signature, handle events, then test, pause, replay and delete.

# Webhooks

Webhooks push events to your server instead of you polling: new blocks, transaction confirmations, address activity and token transfers. You will create a subscription, receive and verify the deliveries, and then manage it. There are **no accounts**: each subscription has its own management token and signing secret.

## Before you start

- A public **`https://`** endpoint on port **443 or 8443** that you control. Private, loopback, link-local and reserved IPs are refused when you create the subscription and again on every delivery, and redirects are not followed.
- A place to store two secrets for each subscription (an environment variable or a secret store).
- Optional: an SDK for verifying signatures: [JavaScript](https://docs.rarebl.com/sdks/js), [Python](https://docs.rarebl.com/sdks/python), [PHP](https://docs.rarebl.com/sdks/php) or [shell](https://docs.rarebl.com/sdks/shell).

## Step 1 — Create a subscription

Choose the event `type` and what to follow. This example follows one transaction until it is final.

```bash cURL
curl -X POST https://api.rarebl.com/open/hooks/create -H "Content-Type: application/json" -d '{
  "type": "tx.confirmations",
  "chain": "btc",
  "txid": "9dedeb8092d6f026d235e3beb24337ba5657635a38cc949f70c0f28ea15e30db",
  "url": "https://example.com/hooks/rarebl",
  "confirmations": 6
}'
```

```js JavaScript
const sub = await rarebl.request('POST', '/open/hooks/create', {
  type: 'tx.confirmations', chain: 'btc',
  txid: '9dedeb8092d6f026d235e3beb24337ba5657635a38cc949f70c0f28ea15e30db',
  url: 'https://example.com/hooks/rarebl', confirmations: 6,
});
```

```python Python
sub = c.post("/open/hooks/create", {
    "type": "tx.confirmations", "chain": "btc",
    "txid": "9dedeb8092d6f026d235e3beb24337ba5657635a38cc949f70c0f28ea15e30db",
    "url": "https://example.com/hooks/rarebl", "confirmations": 6,
})
```

**You should see** the subscription with its `id`, `manageToken` and `signingSecret` (the values here are examples).

```json
{ "id": "hk_3f9a2c71e0b84d56", "manageToken": "mt_7c1d0e94a2b64f3880c5e1a9d27b6f40", "signingSecret": "whsec_b81f4a9c20de7536a1c4f08e93d2b715",
  "type": "tx.confirmations", "chain": "btc", "confirmations": 6, "status": "active" }
```

**Store `manageToken` and `signingSecret` now: both are shown once.**

- `manageToken` authorizes every other hooks call. Send it in the header `X-Hook-Token`, never in a URL.
- `signingSecret` is the key of the HMAC on every delivery.

The event types:

| Type | Needs | Fires |
| --- | --- | --- |
| `block.new` | nothing | every new block, and `block.reorged` on a reorganisation |
| `tx.confirmations` | `txid` | at 0 (seen) and 1, then at each threshold up to `confirmations` (0 to 100, default 6); finally `tx.final` at the chain's reorg depth. Also `tx.dropped` (gone for 24 hours) and `tx.replaced` (RBF, with the new txid) |
| `address.activity` | `address` | in the mempool and at each confirmation threshold. BTC, LTC and BCH addresses are watched automatically |
| `token.transfer` | `address` or `contract` | ETH and TRON token transfers |

[Create a webhook](https://docs.rarebl.com/api/hooks/create) is in the hooks rate class: 10 calls per minute, at most 100 active subscriptions per IP.

## Step 2 — Receive deliveries

Rarebl `POST`s the event to your URL. Read the **raw request body**: you need the exact bytes to verify the signature.

| Header | Meaning |
| --- | --- |
| `X-Rarebl-Event-Id` | A unique event id. Use it to deduplicate. |
| `X-Rarebl-Event-Type` | For example `tx.confirmations`. |
| `X-Rarebl-Signature` | `t=<unix>,v1=<hex>`: the signature to verify. |

The body is `{id, type, chain, created, data, apiVersion: "2"}`. The contents of `data` depend on the event type; the values here are examples.

```json
{
  "id": "evt_01hx9k2p4m",
  "type": "tx.confirmations",
  "chain": "btc",
  "created": 1763160492,
  "data": { "txid": "9dedeb8092d6f026d235e3beb24337ba5657635a38cc949f70c0f28ea15e30db", "confirmations": 1, "blockHeight": 975851 },
  "apiVersion": "2"
}
```

## Step 3 — Verify the signature

`v1` is `HMAC-SHA256(signingSecret, t + "." + body)` in hex, where `t` is the timestamp from the header and `body` is the raw body. Reject a bad signature, and reject old timestamps (the SDKs allow 300 seconds).

```js JavaScript
import express from 'express';
import { webhook } from './rarebl.mjs';

const app = express();
app.post('/hooks/rarebl', express.raw({ type: '*/*' }), (req, res) => {         // the RAW body, not parsed JSON
  if (!webhook.verify(process.env.RAREBL_SIGNING_SECRET, req.body, req.get('X-Rarebl-Signature'))) return res.sendStatus(401);
  const event = JSON.parse(req.body);
  res.sendStatus(200);                                                          // answer first, then process
  queue.push(event);
});
```

```python Python
import os, rarebl
from flask import Flask, request, abort

app = Flask(__name__)

@app.post("/hooks/rarebl")
def hook():
    if not rarebl.webhook.verify(os.environ["RAREBL_SIGNING_SECRET"], request.get_data(),
                                 request.headers.get("X-Rarebl-Signature", ""), 300):
        abort(401)
    event = request.get_json(force=True)
    queue.put(event)                                                            # process after answering
    return "", 200
```

```php PHP
<?php
require 'Rarebl.php';

$raw = file_get_contents('php://input');
$ok = Rarebl\Webhook::verify(getenv('RAREBL_SIGNING_SECRET'), $raw, $_SERVER['HTTP_X_RAREBL_SIGNATURE'] ?? '', 300);
if (!$ok) { http_response_code(401); exit; }
$event = json_decode($raw, true);
http_response_code(200);
```

```bash Shell
rarebl verify-webhook --secret-env RAREBL_SIGNING_SECRET \
  --header "$X_RAREBL_SIGNATURE" --body-file raw-body.txt      # prints valid or invalid; exit 0 or 1
```

**You should see** your handler return 200 for a genuine delivery and 401 for a tampered one. To verify by hand in any language: split the header on `,`, read `t` and `v1`, compute the HMAC over `t + "." + rawBody`, and compare in constant time. Use the raw bytes: re-serialised JSON will not match.

## Step 4 — Handle the event

Answer **2xx within 10 seconds**, then do the real work after responding. Anything else is a failure and is retried at **1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours**, with jitter. After **20 consecutive failures** the subscription is paused.

Because of retries, deliveries can repeat and arrive out of order. So:

1. Deduplicate on `X-Rarebl-Event-Id` and make the handler idempotent.
2. Switch on `type`. Credit at `tx.confirmations` with your own threshold, treat `tx.final` as settled, and react to `tx.dropped` and `tx.replaced`.
3. Do not trust the payload alone for money: confirm with [Transaction status](https://docs.rarebl.com/api/tx/status) or [Transaction](https://docs.rarebl.com/api/tx/get).

Deliveries come from the fixed address **167.71.28.63**, so you can allow it in a firewall. Verify the signature on every request regardless.

## Step 5 — Send a test event

[Test](https://docs.rarebl.com/api/hooks/test) sends a signed sample event to your URL, so you can check your endpoint before real events arrive. Use your `manageToken` in the header.

```bash cURL
curl -X POST https://api.rarebl.com/open/hooks/test -H "Content-Type: application/json" \
  -H "X-Hook-Token: $MANAGE_TOKEN" -d '{"id": "hk_3f9a2c71e0b84d56"}'
```

**You should see** a sample delivery at your endpoint, and the attempt in [Deliveries](https://docs.rarebl.com/api/hooks/deliveries) (the last 100 attempts, with HTTP status and timing).

## Step 6 — Manage the subscription

All of these need the header `X-Hook-Token: <manageToken>` and the subscription `id`.

| Call | Use |
| --- | --- |
| [Get](https://docs.rarebl.com/api/hooks/get) | Read the subscription. |
| [Update](https://docs.rarebl.com/api/hooks/update) | Change the URL or threshold. |
| [Pause](https://docs.rarebl.com/api/hooks/pause), [Resume](https://docs.rarebl.com/api/hooks/resume) | Stop and restart deliveries. After 20 failures, fix your endpoint and resume. |
| [Replay](https://docs.rarebl.com/api/hooks/replay) | Re-send a past event by `eventId`, for example after an outage. |
| [Delete](https://docs.rarebl.com/api/hooks/delete) | Remove it when you are done. |

```bash cURL
curl -X POST https://api.rarebl.com/open/hooks/pause -H "Content-Type: application/json" \
  -H "X-Hook-Token: $MANAGE_TOKEN" -d '{"id": "hk_3f9a2c71e0b84d56"}'
curl -X POST https://api.rarebl.com/open/hooks/replay -H "Content-Type: application/json" \
  -H "X-Hook-Token: $MANAGE_TOKEN" -d '{"id": "hk_3f9a2c71e0b84d56", "eventId": "evt_01hx9k2p4m"}'
```

In **Try It** on this site, the id and token from a create call are kept in your browser and prefilled on these pages (clear them with Reset saved Try It values in the settings menu).

## Common errors

| Reply | What it means | What to do |
| --- | --- | --- |
| `API400` on create | The URL is not `https://` on 443 or 8443, or resolves to a private address. | Use a public HTTPS endpoint. |
| `API404` | The subscription id does not exist (or was deleted). | Check the id; create a new subscription. |
| An error on a manage call | The `X-Hook-Token` header is missing or is not the `manageToken` of that subscription. | Send the `manageToken` from create, in the header. |
| `API429` | The hooks class: 10 per minute. | Wait `Retry-After` seconds. |
| Many failed deliveries | Your endpoint is slow, answers non-2xx or has a bad certificate. | Check [Deliveries](https://docs.rarebl.com/api/hooks/deliveries), fix it, then Resume and Replay. |
| 100 active subscriptions | The per-IP limit. | Delete the ones you no longer need. |

## Next steps

- One subscription per thing you follow; delete it when done.
- Many addresses on BTC, LTC and BCH: an `address.activity` webhook watches them for you ([Watch addresses](https://docs.rarebl.com/guides/watch-addresses)).
- For live dashboards prefer the [WebSocket stream](https://docs.rarebl.com/guides/websocket).
- Credit deposits: [Track a deposit until it confirms](https://docs.rarebl.com/guides/track-a-deposit).

Back to the Rarebl AI documentation index