什么是 webhook?
作者 Uttam Singh

webhook(网络钩子)是以 HTTP POST 请求形式投递的事件通知。当某个系统中发生了某件事,该系统会向你控制的 URL 发送一个请求,请求正文描述发生了什么。你的应用无需主动询问是否有任何变化。
在区块链应用中,事件发生在链上。用户的钱包收到 USDC,某个 NFT 易手,或者你部署的合约发出一条日志。没有 webhook 时,你的后端需要定时轮询节点或 API,并比较结果来捕捉这些变化。有了 webhook,变化一经检测到就会发送到你的服务器。
本文介绍 webhook URL、端点和 payload 分别是什么,webhook 与 API 有何不同,投递和重试如何运作,如何验证请求,以及如何在生产环境中处理重复投递和重组。
什么是 webhook?
webhook 连接两方。发送方监听事件。接收方是你的应用,它对外提供一个 URL。当某个事件与你订阅的内容匹配时,发送方会向该 URL 发出一个 HTTP POST 请求,请求的 JSON 正文描述该事件,你的服务器则以 200 状态码应答,确认已收到。你这一侧不会返回任何数据,因此 webhook 是一种单向的、服务器到服务器的消息。
另一种做法是轮询,即按固定频率向数据源查询更新。这些请求大多不会带回新内容,而且一个事件可能要等上整整一个轮询间隔,才会被你的应用发现。webhook 可以同时避免这两个问题,因为只有在有事件需要报告时才会发出请求。
可以把轮询想象成每小时给商店打一次电话,询问你的订单到了没有。webhook 则是货到时商店给你发一条短信。
什么是 webhook URL?
webhook URL 是发送方投递事件的公网地址,例如 https://api.example.com/webhooks/alchemy。创建 webhook 时,你需要向提供方注册这个地址。请求由提供方的服务器发出,所以该 URL 必须能从互联网访问;而且由于 payload 描述的是你的用户的活动,它应当使用 HTTPS。在开发阶段,ngrok 等隧道工具可以为你笔记本电脑上的服务器提供一个临时的公网 URL。我们的 webhook 快速入门介绍了这一设置过程。
什么是 webhook 端点?
webhook 端点是你服务器上处理发往 webhook URL 的请求的路由。人们常常混用这两个术语。URL 是地址,端点则是地址背后的代码。
端点的职责应尽量精简。它检查请求是否来自提供方,存储事件,然后返回 200。更新余额、发送推送通知等较慢的工作应交给后台工作进程处理。如果这些工作在请求内部执行,一次缓慢的数据库调用就可能让提供方认为投递失败。
什么是 webhook payload?
payload 是请求的 JSON 正文。我们所有的 webhook 都使用相同的外层结构,event 字段存放该 webhook 类型对应的数据。下面是一个 Address Activity webhook(用于监控一组钱包地址的类型)针对 Ethereum 上一笔 USDC 转账发出的 payload。为控制篇幅,部分字段已省略。
{
"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 表示触发的是哪个 webhook,id 标识此次事件,type 指明 webhook 类型。activity 中的每一项都是一笔涉及被监控地址的转账。要识别代币,请使用代币的合约地址 rawContract.address。asset 符号来自代币合约本身,而任何人都可以部署一个自称 USDC 的代币。
webhook 与 API 有什么区别?
API 等待你来调用。你的应用发送请求,然后取回数据。webhook 则反转了方向,因为提供方会在有内容需要报告时调用你的应用。正是这种反转,让 webhook 有时被称为“反向 API”。
大多数生产应用会同时使用两者。webhook 告诉你发生了某件事,再由一次 API 调用补全其余信息。例如,支付后端可能先收到一个充值 webhook,然后调用 Token API 读取钱包的当前余额,再展示给用户。
当消费方是服务器端点时,适合使用 webhook。如果消费方是长期运行的进程,例如交易机器人或实时仪表盘,WebSocket 订阅则会保持一条打开的连接,并通过它流式传输事件。我们的 webhook、WebSocket 与 gRPC 对比介绍了如何在它们之间做选择。
webhook 是如何工作的?
每次 webhook 投递都会经历相同的几个阶段:
- 创建订阅。你创建一个 webhook,指定目标 URL 和一个过滤条件,说明要监控哪个网络以及在该网络上监控什么,例如一组地址或某个合约的事件。
- 事件匹配。某个新区块中包含与你的过滤条件匹配的活动。
- 提供方发送 POST 请求。请求中携带 payload,以及一个可供你的服务器校验的签名请求头。
- 你的服务器确认收到。返回 200 响应即表示该事件已投递成功。
- 失败的投递会被重试。如果你的服务器宕机、超时或返回任何其他状态码,提供方会稍后再次尝试。
在 Alchemy 上,重试采用指数退避,即每次失败后,两次尝试之间的等待时间都会变长。在 Free 和 Pay As You Go 套餐上,我们会持续重试最多 10 分钟;在 Enterprise 套餐上最多持续一小时,详见我们的 webhook 快速入门中关于投递行为的说明。
在每条链上,首次通知都会按顺序投递,无论该链出块速度多快。这一保证只适用于首次尝试,因此一个被重试的事件可能晚于更新的事件到达。请为每个事件存储区块号,并按区块号而不是到达时间对记录排序。
如果你的服务器宕机时间超过了重试窗口,这段时间内的事件不会被重新投递,因此需要从链的历史数据中补齐缺口。在 Ethereum 以及 Transfers API 支持的 L2 上,可以针对你错过的地址和区块范围查询 Transfers API。在 Stellar 上,Stellar 转账端点会返回某个账户按账本范围筛选的转账。在 Solana 上,使用 getSignaturesForAddress 逐页回溯每个被监控地址的交易。
webhook 在区块链应用中有哪些用途?
我们提供四种 webhook 类型,该选哪一种取决于你需要监控什么。
Address Activity webhook 承担最常见的任务,即监控一组钱包。支付后端会监控自己的充值地址,并在 USDC 到账时为用户入账。钱包应用会在用户收到代币时发送推送通知。ERC-20 转账包含代币的合约地址,支付后端正是借此确认一笔充值是真正的 USDC。
NFT Activity webhook 针对 NFT 合约提供同样的功能,适合 NFT 市场和合集仪表盘。Custom webhook 处理其余所有情况。你编写一个 GraphQL 查询来选择所需的区块数据,例如你自己的合约中带有特定事件签名的日志,webhook 便会在每个新区块产生时触发,并且只携带这些数据。Stellar Address Activity webhook 将地址追踪能力带到了 Stellar,包括 Soroban 合约事件。
每个 webhook 只监控一个网络。要覆盖多条链,请为每个网络各创建一个 webhook,并让它们都指向同一个 URL。webhook 也适用于链上 AI 代理:代理可以在被监控的钱包转移资金时被唤醒,而无需循环轮询。
如何验证 webhook 签名?
任何人只要找到你的 webhook URL,都可以向它发送请求,因此你的端点必须先确认每个请求都来自提供方,然后再进行处理。我们使用 HMAC-SHA256 对每个 webhook 请求签名,这是一种带密钥的哈希,只有持有该密钥的人才能重现。密钥是你的 webhook 的签名密钥,输入是请求正文,十六进制结果放在 X-Alchemy-Signature 请求头中。你的服务器对收到的正文计算同样的哈希,并比较这两个值。
每个 webhook 都有自己的签名密钥,显示在 Alchemy 控制台中该 webhook 的页面上。以下三个细节决定了这项检查能否真正保护你:
- 对原始字节计算哈希。签名覆盖的是请求正文发送时的原样内容。如果你的框架解析了 JSON,而你的代码又将其重新序列化,空格和键的顺序可能发生变化,哈希就会对不上。
- 以恒定时间比较。在 Node 中使用
crypto.timingSafeEqual,在 Python 中使用hmac.compare_digest,而不是===或==,这样响应时间就不会泄露伪造签名中有多少部分是正确的。 - 先拒绝,再做其他事。返回 401 并停止处理。不要解析或存储尚未验证的 payload。
下面是一个遵循以上三点的最简 Express 端点:
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);作为第二层防护,你可以在防火墙中只放行我们的 webhook IP 地址。webhook 快速入门的安全部分列出了这些地址。
应用应如何处理重复、延迟或遭遇重组的 webhook?
应将 webhook 投递视为至少一次投递。超时后的重试可能会投递一个你的服务器已经存储过的事件,因此处理器必须是幂等的。同一事件处理两次,应与处理一次的效果相同。
- 按转账去重,而不是按请求去重。一个 payload 可能携带多笔转账,一笔交易也可能包含多笔转账。用网络、交易
hash以及该转账在交易中的位置组成一个键:代币转账的位置是log.logIndex,内部转账的位置是typeTraceAddress。外部转账只需要哈希,因为一笔交易只有一笔外部转账。将该键存入带唯一约束的表中,并且只有当某次投递的键和log.removed值都与已存储的记录一致时,才跳过该投递。 - 先存储,再确认。在事件被持久存储后再返回 200,并在工作进程中完成实际工作。在响应之前就执行工作的处理器有超时的风险,而超时会触发重试,重新投递一个你可能已经处理过的事件。
- 撤销被重组的转账。链重组(reorg)会替换最近的区块,被替换区块中的交易会从链上移除。代币转账带有
log.removed字段,值为true表示该交易已不在规范链中。log.removed为true的投递与原始转账携带相同的键,因此应将其视为对已存储记录的更新。将该记录标记为已移除,并撤销你的应用针对这笔转账所做的一切操作。 - 执行不可逆操作前先等待终局性。立即显示一笔待确认的充值是合理的。但释放资金、发货或计入可提取余额,应等到区块被最终确定之后再进行。在 Ethereum 上,
finalized区块标签会告诉你哪些区块已经达到这一状态。
如何在 Alchemy 上设置 webhook?
先从监听服务开始。我们的 webhook 快速入门提供 JavaScript、Python、Go 和 Rust 的入门服务器,并内置签名验证;webhook 示例仓库则提供可以在本地运行的完整项目。
然后创建 webhook。最快的方式是使用 webhook 控制台。选择类型和网络,添加地址或过滤条件,粘贴你的 URL,然后使用 Test Webhook 按钮向你的端点发送一个示例事件。
要通过代码创建 webhook,请使用 webhook 控制台顶部显示的认证令牌调用 Notify API:
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"]
}'之后你可以通过同一个 API 添加和移除地址,因此为每个新用户创建充值地址的应用只需维护一个 webhook,而不必创建成千上万个。Alchemy CLI 通过 alchemy webhook create、list、update 和 delete 封装了同一个 API,适合由编程代理管理你的 webhook 的场景。
查看 Alchemy 控制台的实际使用效果
30 分钟引导演示:现场演示控制台中的每一个工具。
Watch the demo开始在 Alchemy 上使用 webhook
Alchemy Webhooks 在 Free 套餐中即可使用,无需签约,也没有最低消费承诺。第一个 webhook 不妨选择一个监控你自己所控制钱包的 Address Activity webhook。向该钱包发送一笔小额转账,观察 payload 到达你的端点。在此基础上,Custom webhook 可以让你以相同的投递、重试和签名机制监控你自己合约的事件。
常见问题
简单来说,webhook 是什么?
webhook 是某件事发生时,一个应用自动发送给另一个应用的消息。发送方应用向你提供的 URL 发出 HTTP POST 请求,并在请求正文中附上事件详情。你的应用无需反复询问是否有更新,因为更新会被主动发送给它。
webhook 和 API 有什么区别?
使用 API 时,你的应用发送请求,服务器以数据作为响应。使用 webhook 时,服务器会在事件发生时主动向你的应用发送请求,无需你来询问。API 适合按需读取数据,webhook 适合响应事件。大多数应用会同时使用两者:先接收 webhook,再调用 API 获取详细信息。
USDC 到达我的钱包时,如何收到 webhook 通知?
在你接收 USDC 的链上创建一个 Alchemy Address Activity webhook,添加你的钱包地址,并将其指向你服务器的 URL。每笔转入的转账都会附带发送方、金额和代币合约地址。请按 USDC 的合约地址进行匹配,而不是按“USDC”这个符号,因为任何人都可以部署同名代币。
如何接收 Solana 钱包活动的 webhook 通知?
Alchemy 的 Address Activity webhook 除了支持 30 多条 EVM 链外,还支持 Solana,因此你可以监控一组 Solana 钱包地址,并在这些地址有活动时收到 POST 请求。Address Activity webhook 文档中提供了 Solana 专用设置指南的链接。处理每个请求之前,请先验证其签名。
我的智能合约发出事件时,webhook 能通知我吗?
可以。Alchemy Custom webhook 接受 GraphQL 查询,因此你可以筛选来自你的合约地址、带有特定事件签名的日志。之后,webhook 会在每个新区块产生时触发,并且只携带匹配的日志。每个 Custom webhook 只监控一个网络,因此请在部署了合约的每条链上各创建一个。
webhook 触发时如果我的服务器宕机了,会怎样?
Alchemy 会以指数退避方式重试失败的投递,在 Free 和 Pay As You Go 套餐上最多持续 10 分钟,在 Enterprise 套餐上最多持续一小时。只要你的服务器没有返回 200,该次投递即视为失败。如果服务器宕机时间更长,可以从链的历史数据中回填错过的活动:在 Ethereum 和受支持的 L2 上使用 Transfers API,在 Solana 和 Stellar 上使用各链自己的历史数据端点。
一个 webhook 可以追踪多少个地址?
一个 Alchemy Address Activity webhook 最多可追踪 100,000 个地址,Stellar Address Activity webhook 也是如此。你可以通过 Notify API 添加和移除地址,而无需重新创建 webhook,这适合为每个用户创建新充值地址的应用。
相关概览

构建区块链应用
Alchemy 将最强大的 Web3 开发者产品和工具与资源、社区及专业支持结合在一起。


