Skip to content
0%

What is a webhook?

Uttam Singh headshot

Written by Uttam Singh

Published on October 29, 20269 min read

What is a webhook?

A webhook is an event notification delivered as an HTTP POST. When something happens in one system, that system sends a request to a URL you control, and the request body describes what happened. Your app never has to ask whether anything changed.

In blockchain apps, the events happen onchain. A user's wallet receives USDC, an NFT changes hands, or a contract you deployed emits a log. Without a webhook, your backend polls a node or an API on a timer and compares the results to catch those changes. With one, the change is sent to your server as soon as it is detected.

This guide covers what a webhook URL, endpoint, and payload are, how a webhook differs from an API, how delivery and retries work, how to verify a request, and how to handle duplicates and reorgs in production.

What is a webhook?

A webhook connects two parties. The sender watches for events. The receiver is your app, which exposes a URL. When an event matches what you subscribed to, the sender makes an HTTP POST request to that URL with a JSON body describing the event, and your server answers with a 200 status code to confirm it arrived. No data comes back from your side, so a webhook is a one-way, server-to-server message.

The alternative is polling, which means asking the source for updates on a fixed schedule. Most of those requests come back with nothing new, and an event can sit unnoticed for up to a full polling interval before your app sees it. A webhook avoids both problems, because a request is only made when there is an event to report.

Think of polling as calling a store every hour to ask whether your order has arrived. A webhook is the store texting you when it does.

What is a webhook URL?

A webhook URL is the public address the sender delivers events to, such as https://api.example.com/webhooks/alchemy. You register it with the provider when you create the webhook. The provider's servers make the request, so the URL has to be reachable from the internet, and it should use HTTPS because the payload describes your users' activity. During development, a tunneling tool such as ngrok gives a server on your laptop a temporary public URL. Our webhooks quickstart walks through that setup.

What is a webhook endpoint?

A webhook endpoint is the route on your server that handles requests sent to the webhook URL. People use the two terms interchangeably. The URL is the address, and the endpoint is the code behind it.

Keep the endpoint's job small. It checks that the request came from the provider, stores the event, and returns a 200. Slower work, such as updating balances or sending push notifications, belongs in a background worker. If that work runs inside the request, a slow database call can make the provider think the delivery failed.

What is a webhook payload?

The payload is the JSON body of the request. All our webhooks share the same envelope, and the event field holds the data for that webhook type. Here is a payload from an Address Activity webhook, the type that watches a list of wallet addresses, for a USDC transfer on Ethereum. Some fields are trimmed for length.

json
Copied
{ "webhookId": "wh_k63lg72rxda78gce", "id": "whevt_vq499kv7elmlbp2v", "createdAt": "2024-01-23T07:42:26.411977228Z", "type": "ADDRESS_ACTIVITY", "event": { "network": "ETH_MAINNET", "activity": [ { "blockNum": "0xdf34a3", "hash": "0x7a4a39da2a3fa1fc2ef88fd1eaea070286ed2aba21e0419dcfb6d5c5d9f02a72", "fromAddress": "0x503828976d22510aad0201ac7ec88293211d23da", "toAddress": "0xbe3f4b43db5eb49d1f48f53443b9abce45da3b79", "value": 293.092129, "asset": "USDC", "category": "token", "rawContract": { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "decimals": 6 }, "log": { "logIndex": "0x6e", "removed": false } } ] } }

webhookId tells you which webhook fired, id identifies this event, and type names the webhook type. Each entry in activity is one transfer that touched a watched address. To identify the token, use rawContract.address, the token's contract address. The asset symbol comes from the token contract itself, and anyone can deploy a token that calls itself USDC.

How is a webhook different from an API?

An API waits for you to call it. Your app sends a request and gets data back. A webhook reverses the direction, because the provider calls your app when it has something to report. That reversal is why webhooks are sometimes called reverse APIs.

API request
Polling
Webhook
Who starts the exchange
Your app
Your app, on a timer
The provider
When data moves
When you ask
Every interval, even with no changes
Only when an event occurs
Typical use
Reading current state on demand
Simple checks where some delay is acceptable
Reacting to events on your server

Most production apps use both. The webhook tells you that something happened, and an API call fills in the rest. A payments backend might receive a deposit webhook, then call the Token API to read the wallet's current balance before showing it to the user.

Webhooks fit when the consumer is a server endpoint. If the consumer is a long-running process, such as a trading bot or a live dashboard, a WebSocket subscription keeps one connection open and streams events over it instead. Our comparison of webhooks, WebSockets, and gRPC covers how to choose between them.

How do webhooks work?

A webhook delivery moves through the same stages every time:

  • You subscribe. You create a webhook with a destination URL and a filter that says which network to watch and what to watch on it, such as a list of addresses or a contract's events.
  • An event matches. A new block contains activity that matches your filter.
  • The provider sends a POST. The request carries the payload and a signature header your server can check.
  • Your server acknowledges it. A 200 response marks the event as delivered.
  • Failed deliveries are retried. If your server is down, times out, or returns any other status, the provider tries again later.

On Alchemy, retries use exponential backoff, which means the wait between attempts grows after each failure. We keep retrying for up to 10 minutes on Free and Pay As You Go plans and up to an hour on Enterprise, as our webhooks quickstart describes under delivery behavior.

First-time notifications are delivered in order on every chain, however fast it produces blocks. That guarantee applies to first attempts, so a retried event can arrive after newer ones. Store the block number with each event and order your records by it rather than by arrival time.

If your server stays down past the retry window, the events from that period are not redelivered, so fill the gap from the chain's history. On Ethereum and the L2s it supports, query the Transfers API for the addresses and block range you missed. On Stellar, the Stellar transfers endpoint returns an account's transfers filtered by ledger range. On Solana, page back through each watched address's transactions with getSignaturesForAddress.

What are webhooks used for in blockchain apps?

We offer four webhook types, and the right one depends on what you need to watch.

Webhook type
What it tracks
Networks
Address Activity
Native token, ERC-20, ERC-721, and ERC-1155 transfers to or from up to 100,000 addresses
30+ EVM chains and Solana
NFT Activity
Transfers of one NFT, a whole collection, or every NFT on a chain
Ethereum, Arbitrum, Optimism, and Polygon
Custom
Any block, transaction, or log data you select with a GraphQL query
One network per webhook
Stellar Address Activity
XLM and Stellar asset transfers, DEX fills, and Soroban contract events for up to 100,000 accounts
Stellar

Address Activity webhooks cover the most common job, which is watching a set of wallets. A payments backend watches its deposit addresses and credits a user when USDC arrives. A wallet app sends a push notification when a user receives tokens. ERC-20 transfers include the token's contract address, which is how the payments backend confirms that a deposit is real USDC.

NFT Activity webhooks do the same for NFT contracts, which suits marketplaces and collection dashboards. Custom webhooks handle everything else. You write a GraphQL query that selects the block data you want, such as logs from your own contract with a specific event signature, and the webhook fires for each new block with only that data. Stellar Address Activity webhooks bring address tracking to Stellar, including Soroban contract events.

Each webhook watches one network. To cover several chains, create one webhook per network and point them all at the same URL. Webhooks also suit onchain AI agents, which can wake up when a watched wallet moves funds instead of polling in a loop.

How do you verify a webhook signature?

Anyone who finds your webhook URL can send it a request, so your endpoint has to confirm that each request came from the provider before acting on it. We sign every webhook request with HMAC-SHA256, a keyed hash that only someone holding the secret key can reproduce. The key is your webhook's signing key, the input is the request body, and the hex result goes in the X-Alchemy-Signature header. Your server computes the same hash over the body it received and compares the two values.

Each webhook has its own signing key, shown on that webhook's page in the Alchemy dashboard. Three details decide whether the check protects you:

  • Hash the raw bytes. The signature covers the body exactly as it was sent. If your framework parses the JSON and your code serializes it again, spacing and key order can change, and the hashes won't match.
  • Compare in constant time. Use crypto.timingSafeEqual in Node or hmac.compare_digest in Python instead of === or ==, so response timing doesn't reveal how much of a forged signature was correct.
  • Reject before doing anything else. Return a 401 and stop. Don't parse or store a payload you haven't verified.

Here is a minimal Express endpoint that follows all three:

typescript
Copied
import express from "express"; import { createHmac, timingSafeEqual } from "node:crypto"; // enqueue is your own durable queue (a database table, SQS, BullMQ). // It should resolve only after the event is safely stored. import { enqueue } from "./queue"; const signingKey = process.env.ALCHEMY_WEBHOOK_SIGNING_KEY ?? ""; if (!signingKey) throw new Error("Set ALCHEMY_WEBHOOK_SIGNING_KEY"); function isValidSignature(rawBody: Buffer, signature: string | undefined): boolean { if (!signature) return false; const expected = createHmac("sha256", signingKey).update(rawBody).digest(); const received = Buffer.from(signature, "hex"); return received.length === expected.length && timingSafeEqual(received, expected); } const app = express(); // express.raw() keeps the exact bytes Alchemy signed, so don't parse JSON on this route first. app.post("/webhooks/alchemy", express.raw({ type: "*/*" }), async (req, res) => { if (!isValidSignature(req.body, req.get("x-alchemy-signature"))) { res.sendStatus(401); return; } await enqueue(JSON.parse(req.body.toString("utf8"))); res.sendStatus(200); }); app.listen(8080);

As a second layer, you can allow only our webhook IP addresses through your firewall. The security section of the webhooks quickstart lists them.

How should your app handle duplicate, late, or reorged webhooks?

Treat webhook delivery as at-least-once. A retry after a timeout can deliver an event your server already stored, so the handler has to be idempotent. Processing the same event twice should have the same effect as processing it once.

  • Deduplicate by transfer, not by request. One payload can carry several transfers, and one transaction can contain several too. Build a key from the network, the transaction hash, and the transfer's position in the transaction, which is log.logIndex for token transfers and typeTraceAddress for internal transfers. An external transfer needs only the hash, since a transaction has one. Store the key in a table with a unique constraint, and skip a delivery only when its key and its log.removed value both match the stored record.
  • Store first, then acknowledge. Return the 200 once the event is durably stored, and do the real work in a worker. A handler that does the work before responding risks a timeout, and the timeout triggers a retry of an event you may have already acted on.
  • Reverse reorged transfers. A chain reorganization, or reorg, replaces recent blocks, and transactions in the replaced blocks drop out of the chain. Token transfers carry a log.removed field, and true means the transaction is no longer in the canonical chain. A delivery with log.removed set to true carries the same key as the original transfer, so treat it as an update to the stored record. Mark the record removed and undo whatever your app did for that transfer.
  • Wait for finality before irreversible actions. Showing a pending deposit right away is reasonable. Releasing funds, shipping goods, or crediting a withdrawable balance should wait until the block is finalized. On Ethereum, the finalized block tag tells you which blocks have reached that point.

How do you set up a webhook on Alchemy?

Start with the listener. Our webhooks quickstart has starter servers in JavaScript, Python, Go, and Rust with signature verification built in, and the webhook examples repository has complete projects you can run locally.

Then create the webhook. The webhooks dashboard is the fastest route. Pick the type and network, add the addresses or filter, paste your URL, and use the Test Webhook button to send a sample event to your endpoint.

To create webhooks from code, call the Notify API with the auth token shown at the top of your webhooks dashboard:

bash
Copied
curl -X POST https://dashboard.alchemy.com/api/create-webhook \ -H "X-Alchemy-Token: $ALCHEMY_WEBHOOK_AUTH_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "network": "BASE_MAINNET", "webhook_type": "ADDRESS_ACTIVITY", "webhook_url": "https://api.example.com/webhooks/alchemy", "addresses": ["0xYOUR_DEPOSIT_ADDRESS"] }'

You can add and remove addresses later through the same API, so an app that creates a deposit address for every new user keeps one webhook rather than creating thousands. The Alchemy CLI wraps the same API with alchemy webhook create, list, update, and delete, which is useful when a coding agent manages your webhooks.

Alchemy Dashboard Demo
Free video demo

See the Alchemy dashboard in action

30-minute guided walkthrough: every tool in the dashboard, demonstrated live.

Watch the demo

Start using webhooks on Alchemy

Alchemy Webhooks are available on the Free plan, with no contract and no minimum commitment. A good first webhook is an Address Activity webhook on a wallet you control. Send it a small transfer and watch the payload reach your endpoint. From there, Custom webhooks let you watch your own contracts' events with the same delivery, retry, and signature behavior.

Frequently asked questions

What is a webhook in simple terms?

A webhook is an automatic message one app sends to another when something happens. The sending app makes an HTTP POST request to a URL you provide, with the details of the event in the request body. Your app doesn't have to keep asking for updates, because the update is sent to it.

What is the difference between a webhook and an API?

With an API, your app sends a request and the server responds with data. With a webhook, the server sends your app a request when an event happens, without being asked. APIs suit reading data on demand, and webhooks suit reacting to events. Most apps use both, receiving a webhook and then calling an API for details.

How do I get a webhook notification when USDC lands in my wallet?

Create an Alchemy Address Activity webhook on the chain where you receive USDC, add your wallet address, and point it at your server's URL. Each incoming transfer arrives with the sender, amount, and token contract address. Match on USDC's contract address rather than the "USDC" symbol, because anyone can deploy a token with that name.

How do I get webhook notifications for Solana wallet activity?

Alchemy's Address Activity webhook supports Solana in addition to 30+ EVM chains, so you can watch a set of Solana wallet addresses and receive a POST request when they have activity. The Address Activity webhook docs link to the Solana-specific setup guide. Verify each request's signature before processing it.

Can a webhook notify me when my smart contract emits an event?

Yes. Alchemy Custom webhooks take a GraphQL query, so you can filter for logs from your contract's address with a specific event signature. The webhook then fires for each new block with only the matching logs. Each Custom webhook watches one network, so create one per chain where your contract is deployed.

What happens if my server is down when a webhook fires?

Alchemy retries failed deliveries with exponential backoff, for up to 10 minutes on Free and Pay As You Go plans and up to an hour on Enterprise. A delivery fails when your server doesn't return a 200. If your server is down for longer than that, backfill the missed activity from chain history, using the Transfers API on Ethereum and supported L2s and each chain's own history endpoint on Solana and Stellar.

How many addresses can one webhook track?

An Alchemy Address Activity webhook tracks up to 100,000 addresses, and so does a Stellar Address Activity webhook. You can add and remove addresses through the Notify API without recreating the webhook, which suits apps that create a new deposit address for every user.

Background gradient

Build blockchain magic

Alchemy combines the most powerful web3 developer products and tools with resources, community and legendary support.