# Guide to Alchemy Webhooks: Features and Best Practices

Alchemy Notify webhooks push blockchain activity to your backend: an address you're watching transacts, a transaction is mined or dropped, an NFT changes hands. If you're building on-chain-aware applications, webhooks are how you react in real time without polling nodes.

This guide covers how Alchemy webhooks work, the webhook types, how to verify the `X-Alchemy-Signature` (with the per-webhook signing key), the retry behavior, and the best practices for production.

## What are Alchemy webhooks?

Alchemy Notify delivers HTTP POSTs to a URL you configure per webhook, each carrying blockchain event data scoped to a chain/network. Alchemy signs every delivery with an `X-Alchemy-Signature` header. The important nuance: the signing key is per-webhook, not per-app, so each webhook you create has its own key.

## Alchemy webhook features

| Feature | Details |
| --- | --- |
| Configuration | Notify dashboard or Notify API (scoped per chain/network) |
| Signature header | `X-Alchemy-Signature` |
| Signature scheme | HMAC-SHA256 over the raw body, hex, keyed with the per-webhook signing key |
| Signing key | Per-webhook (not per-app); on the webhook's detail page or via the API |
| Retries | Exponential backoff up to ~10 min (Free/PAYG) or ~1 hr (Enterprise) |
| IP allowlist | Optional: `54.236.136.17`, `34.237.24.169` |
| SDK | `alchemy-sdk` (`NotifyNamespace`) manages CRUD, but does not verify |

## Webhook types

Alchemy's webhook types are the on-the-wire enum values:

| Type | Fires when |
| --- | --- |
| `ADDRESS_ACTIVITY` | A watched address has activity (transfers in/out) |
| `MINED_TRANSACTION` | A submitted transaction is mined |
| `DROPPED_TRANSACTION` | A submitted transaction is dropped |
| `NFT_ACTIVITY` | An NFT transfer occurs |
| `NFT_METADATA_UPDATE` | NFT metadata updates |
| `GRAPHQL` | A Custom Webhook (GraphQL) fires on your defined query |

Subscribe to the type you need, scoped to the right chain/network.

## Setting up Alchemy webhooks

Create webhooks in the Notify dashboard or via the Notify API (which uses your app Auth Token, distinct from the signing key). Each webhook is scoped per chain/network. Copy the per-webhook signing key from the top-right of that webhook's detail page (or fetch it via the API); this is what you verify with. Optionally allowlist Alchemy's egress IPs (`54.236.136.17`, `34.237.24.169`).

## Securing Alchemy webhooks

Each delivery carries an `X-Alchemy-Signature` header: a hex-encoded HMAC-SHA256 of the raw request body, keyed with that webhook's signing key. Alchemy's docs stress this repeatedly: verify against the raw body, never a JSON-reparsed version.

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

// Per-WEBHOOK signing key (each webhook has its own)
const SIGNING_KEY = process.env.ALCHEMY_WEBHOOK_SIGNING_KEY;

function verify(rawBody, signature) {
  const expected = crypto
    .createHmac("sha256", SIGNING_KEY)
    .update(rawBody, "utf8")
    .digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.headers["x-alchemy-signature"])) {
    return res.sendStatus(401);
  }

  res.sendStatus(200); // acknowledge fast
  processQueue.add(JSON.parse(req.body)); // process asynchronously
});

```

The same check in Python:

```python
import hashlib
import hmac
import os

SIGNING_KEY = os.environ["ALCHEMY_WEBHOOK_SIGNING_KEY"].encode()

def verify(raw_body: bytes, signature: str) -> bool:
    expected = hmac.new(SIGNING_KEY, raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

```

The `alchemy-sdk` (`NotifyNamespace`) manages webhook CRUD but doesn't verify signatures, so you implement this check yourself.

## Alchemy webhook limitations and pain points

### The signing key is per-webhook, not per-app

The Problem: Each webhook has its own signing key. Verifying with an app-wide key (or reusing one webhook's key for another) rejects legitimate deliveries.

Why It Happens: Alchemy scopes signing keys to individual webhooks.

Workarounds:

* Store the correct signing key per webhook and select it by the receiving endpoint.

How Hookdeck Can Help: Hookdeck verifies each source with its own key at the edge, so your application receives pre-verified events without juggling per-webhook keys.

### Raw-body sensitivity

The Problem: The HMAC is over the raw body. Any middleware that parses and re-serializes the JSON changes the bytes and breaks verification, an issue Alchemy's docs call out repeatedly.

Why It Happens: JSON reserialization doesn't preserve the exact bytes signed.

Workarounds:

* Capture the raw body (`express.raw`, `request.get_data()`) and verify before parsing.

How Hookdeck Can Help: Hookdeck verifies against the bytes as received, so your app never has to preserve the raw body itself.

### Retries and duplicates

The Problem: Alchemy retries with exponential backoff (up to ~10 min on Free/PAYG, ~1 hr on Enterprise), so the same event can arrive more than once. On-chain reorgs can also produce related events.

Why It Happens: At-least-once delivery favors eventual delivery.

Workarounds:

* Dedupe on the event/webhook delivery ID, and make on-chain side effects idempotent and reorg-aware.

How Hookdeck Can Help: Hookdeck deduplicates deliveries at the edge, so retries don't double-process. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### High-volume address activity

The Problem: Watching busy addresses or broad NFT activity can generate high webhook volume that overwhelms a slow endpoint.

Why It Happens: On-chain activity is bursty and high-frequency.

Workarounds:

* Acknowledge fast and process off a queue; scope webhooks narrowly to the addresses/networks you need.

How Hookdeck Can Help: Hookdeck absorbs bursts and forwards at a rate your endpoint can handle, so activity spikes don't cause failed deliveries.

## Best practices

### Verify with the per-webhook key over the raw body

Compute hex HMAC-SHA256 over the raw body with that webhook's signing key and compare in constant time.

### Allowlist Alchemy's IPs

Optionally restrict the endpoint to Alchemy's egress IPs (`54.236.136.17`, `34.237.24.169`) for defense in depth.

### Acknowledge fast, process asynchronously

Return 200 immediately and defer work to a queue. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

### Dedupe and be reorg-aware

Dedupe on the delivery ID and make on-chain side effects idempotent so retries and reorgs don't double-process.

## Conclusion

Alchemy Notify webhooks push on-chain activity to your backend, verified with an `X-Alchemy-Signature` HMAC over the raw body, keyed with a per-webhook signing key. The details that matter are using the right per-webhook key, verifying against the raw bytes, and handling bursty high volume plus retries and reorgs.

[Hookdeck](https://hookdeck.com) verifies the signature, deduplicates, absorbs bursts, and durably queues every event at the edge, so your app only ever processes verified, unique events.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Alchemy webhooks reliably in minutes.