> For the complete documentation index, see [llms.txt](https://docs.mercurydata.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.mercurydata.app/mercury-classic/webhooks.md).

# Webhooks

Mercury can deliver real-time contract event notifications to your HTTP endpoint via webhooks. When an event matches your webhook's filters, Mercury sends a POST request with\
the event data, signed with HMAC-SHA256 for authenticity.

#### Creating a webhook

`contract_ids` accepts one or more contract IDs. Mercury creates one webhook subscription per contract ID and returns all assigned IDs in the response.

```bash
curl https://mainnet.mercurydata.app/rest/webhooks/new \
  -H 'Authorization: Bearer <your-jwt>' \
  -H 'Content-Type: application/json' \
  -d '{
    "webhook_endpoint": "https://your-endpoint.com/webhook",
    "contract_ids": ["CDVNV4...", "CDDNV3..."]
  }'
```

Response:

```bash
{
  "ids": [42, 43],
  "secret": "whsec_a1b2c3d4e5f6..."
}
```

Save the secret -- it's shown only once. Use it to verify incoming webhook signatures.

#### Supplying your own secret

By default Mercury generates a random HMAC secret. You can supply your own by passing `webhook_secret`:

```bash
curl https://mainnet.mercurydata.app/rest/webhooks/new \
  -H 'Authorization: Bearer <your-jwt>' \
  -H 'Content-Type: application/json' \
  -d '{
    "webhook_endpoint": "https://your-endpoint.com/webhook",
    "contract_ids": ["CDVNV4..."],
    "webhook_secret": "my-custom-secret"
  }'
```

The provided value is used as-is and returned in the `secret` field of the response.

#### Topic filtering

By default, a webhook fires for all events from the specified contract. To filter by specific event topics, pass topic1 through topic4 as XDR base64-encoded values:

```bash
curl -X POST https://testnet.mercurydata.app/rest/webhooks/new \
    -H "Authorization: Bearer <your-jwt>" \
    -H "Content-Type: application/json" \
    -d '{
      "webhook_endpoint": "https://your-endpoint.com/webhook",
      "contract_ids": ["CAA3TVC..."],
      "topic1": "AAAADwAAAAhwdXJjaGFzZQ==",
      "topic2": "AAAAEgAAAAAAAAAAYK5vRsE..."
    }'
```

Filtering rules:

* Each topic filter is optional. Omitted topics act as wildcards (match anything).
* All specified topics use AND logic — every filter must match for the event to trigger delivery.
* You can filter on any combination: just topic1, just topic3, both topic1 and topic4, etc.
* Topic values must match exactly (XDR base64 encoding of the ScVal).

#### Listing webhooks

```bash
curl https://testnet.mercurydata.app/rest/webhooks/list \                                                                                                                                 
    -H "Authorization: Bearer <your-jwt>"    
```

Response:

{% code overflow="wrap" %}

```bash
[
    {
      "id": 42,
      "webhook_endpoint": "https://your-server.com/webhook",
      "contract_id": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
      "topic1": "AAAADwAAAAh0cmFuc2Zlcg==",
      "topic2": null,
      "topic3": null,
      "topic4": null
    }
  ]
```

{% endcode %}

#### Deleting a webhook

```bash
curl -X DELETE https://testnet.mercurydata.app/rest/webhooks/{id} \                                                                                                                       
    -H "Authorization: Bearer <your-jwt>"
```

#### Webhook payload

When an event matches, Mercury sends a POST request to your endpoint:

```bash
  {
    "event": {
      "ext": "v0",
      "contract_id": "CDVNV4HBU5WBEQO5K7YZW7NULFLHOTYMUMYUGSPAU2QOADZRIJYTYMM6",
      "type_": "contract",
      "body": {
        "v0": {
          "topics": [
            { "symbol": "deposit" },
            { "address": "GAGAXGWEMQZ4NLHXDRSGXVMX2XENYHQVWSQC5R5U6YNXKBUP7FQ2SCVR" },
            { "i32": 28 }
          ],
          "data": {
            "i128": "100000"
          }
        }
      }
    },
    "tx_hash": "14ff90e64e533d87f5a11a961edd4d7d176e7953767c4532d01e4b0...."
  }
```

#### Headers

* X-Mercury-Signature: HMAC-SHA256 hex signature of the request body
* X-Mercury-Timestamp: Unix timestamp (seconds) when the request was sent
* Content-Type: application/json

#### Verifying signatures

Optionally use your webhook secret to verify that requests are authentic:

*Node.js*

```javascript
const crypto = require('crypto');

  function verifyWebhook(body, signature, secret) {
    const expected = crypto
      .createHmac('sha256', secret)
      .update(body)
      .digest('hex');
    return crypto.timingSafeEqual(
      Buffer.from(signature, 'hex'),
      Buffer.from(expected, 'hex')
    );
  }

  // In your handler:
  app.post('/webhook', (req, res) => {
    const rawBody = JSON.stringify(req.body);
    const signature = req.headers['x-mercury-signature'];

    if (!verifyWebhook(rawBody, signature, 'whsec_your_secret_here')) {
      return res.status(401).send('Invalid signature');
    }

    // Process the event
    console.log('Received event:', req.body.event);
    res.status(200).send('OK');
  });
```

*Python*

```python
  import hmac
  import hashlib

  def verify_webhook(body: str, signature: str, secret: str) -> bool:
      expected = hmac.new(
          secret.encode(), body.encode(), hashlib.sha256
      ).hexdigest()
      return hmac.compare_digest(signature, expected)
```
