如何为个人JavaScript/Node项目自定义开发Webhook功能?
核心原理澄清
你对Webhook的理解存在部分偏差:Webhook本质是服务端到服务端的单向回调机制,即外部服务处理完任务后,主动调用你提前提供的服务端接口通知结果,它本身不具备直接给客户端推送多次响应的能力。你理解的「客户端发一次请求就能接收服务端多次响应」是服务端与客户端之间的实时通信能力,和Webhook是两个独立的技术环节。
另外Webhook完全可以自定义开发,并非只能由第三方服务提供,你只需要在自己的服务端写一个可供外部服务调用的POST接口,这就是属于你的自定义Webhook。
业务流程完整实现方案
1. 开发服务端Webhook接口
这个接口就是你提供给外部API的回调地址,属于普通的Express POST接口,示例代码如下:
// Express 服务端代码 // 部署后你的Webhook地址为 https://你的域名/api/webhook/external-callback app.post('/api/webhook/external-callback', express.json(), async (req, res) => { // 第一步:校验请求来源合法性,避免恶意请求(对接的外部API一般会提供签名校验规则) // const isValid = verifyExternalSignature(req); // if(!isValid) return res.sendStatus(403); const processedResult = req.body; // 外部API返回的处理结果 // 存储结果到数据库 await db.processedResults.insert(processedResult); // 必须给外部API返回200状态码,否则它会认为回调失败触发重试 res.sendStatus(200); // 推送新结果到前端 sendDataToClient(processedResult); // 检查是否还有未提交的待处理数据,有就提交下一批 const nextBatch = await db.pendingData.takeBatch(10); if(nextBatch.length > 0) { await fetch('外部API提交地址', { method: 'POST', body: JSON.stringify(nextBatch), headers: { 'Content-Type': 'application/json' } }) } })
2. 搭建服务端到前端的实时推送通道
要实现服务端收到Webhook回调后主动推数据给前端,轻量方案可选SSE(服务器发送事件),符合你的单向多次推送需求,服务端实现示例:
// 存储所有已连接的客户端SSE响应对象 const connectedClients = new Map(); // SSE连接接口,前端页面加载时就发起连接 app.get('/api/realtime-stream', (req, res) => { // 设置SSE required响应头 res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); // 生成当前客户端唯一标识 const clientId = Date.now().toString(); connectedClients.set(clientId, res); // 客户端断开连接时清理资源 req.on('close', () => { connectedClients.delete(clientId); }); }) // Webhook回调里调用的推送方法 function sendDataToClient(data) { connectedClients.forEach((clientRes) => { // 按SSE规范格式发送数据 clientRes.write(`data: ${JSON.stringify(data)}\n\n`); }) }
3. React前端逻辑实现
import { useEffect, useState } from 'react'; function DataPage() { const [allData, setAllData] = useState([]); useEffect(() => { // 页面加载时拉取初始数据 fetch('/api/init-data') .then(res => res.json()) .then(initData => setAllData(initData)); // 建立SSE长连接接收推送 const eventSource = new EventSource('/api/realtime-stream'); eventSource.onmessage = (event) => { const newResult = JSON.parse(event.data); setAllData(prev => [...prev, newResult]); } // 组件卸载时关闭连接 return () => eventSource.close(); }, []); // 点击按钮触发第一批数据提交,用POST请求即可 const handleStart = async () => { await fetch('/api/start-process', { method: 'POST' }); } return ( <div> <button onClick={handleStart}>开始批量处理</button> {allData.map(item => <div key={item.id}>{item.content}</div>)} </div> ) }
注意事项
- Webhook接口一定要做请求来源校验,避免恶意请求篡改你的业务数据
- 要做好回调结果去重,外部API在回调超时/失败时一般会触发重试,避免重复处理同一份结果
- 如果你的服务端是多实例部署,需要搭配Redis发布订阅能力同步跨实例的推送消息,保证所有在线客户端都能收到数据
内容的提问来源于stack exchange,提问作者Franco Ortega
相关产品推荐
相关产品推荐

