creatorpiyush/verihook

GitHub: creatorpiyush/verihook

提供统一的强类型 API,用于跨 12+ 主流服务商验证 webhook 签名,零依赖且支持多种 JS 运行时和边缘环境。

Stars: 1 | Forks: 0

# verihook 🪝 [![npm version](https://img.shields.io/npm/v/verihook.svg)](https://www.npmjs.com/package/verihook) [![license](https://img.shields.io/npm/l/verihook.svg)](https://github.com/creatorpiyush/verihook/blob/main/LICENSE) `verihook` 提供了一个统一的、强类型的 API,用于验证流行服务的 webhook 签名(**Stripe, GitHub, Shopify, Slack, Twilio, Svix/Resend/Clerk, Meta/WhatsApp, Discord, Linear, Razorpay, Zoom, Square 以及自定义 webhook**)。 无需再为每个服务寻找定制的 HMAC 代码片段,也无需为了验证传入的 webhook 而安装 10 个沉重的 SDK 依赖! 📖 阅读完整的[架构与技术规范](./ARCHITECTURE.md)。 ## 功能 - ⚡ **零运行时依赖**:由标准的 Web Crypto API (`crypto.subtle`) 驱动,并带有 Node.js 回退机制。 - 🌐 **Edge 就绪**:可在任何地方运行 —— Node.js, Vercel Edge, Cloudflare Workers, Deno, Bun, Next.js, Hono, Express, Fastify。 - 🔐 **时间安全**:开箱即用地防范侧信道计时攻击。 - 🎯 **统一的类型化 API**:所有 provider 均提供简单的 `verifyWebhook(provider, req, secret)` 接口。 - ⏳ **重放攻击保护**:内置可配置的时间戳容差检查(Stripe, Slack, Svix, Zoom)。 - 🔌 **可扩展的插件系统**:可通过 `registerProvider()` 注册自定义的 provider 验证器。 ## 安装 ``` npm install verihook # 或 pnpm add verihook # 或 yarn add verihook # 或 bun add verihook ``` ## 支持的 Provider | Provider | 标识符 | 必需的 Header / 说明 | | :--- | :--- | :--- | | **Stripe** | `'stripe'` | `stripe-signature` | | **GitHub** | `'github'` | `x-hub-signature-256` 或 `x-hub-signature` | | **Shopify** | `'shopify'` | `x-shopify-hmac-sha256` | | **Slack** | `'slack'` | `x-slack-signature`, `x-slack-request-timestamp` | | **Twilio** | `'twilio'` | `x-twilio-signature`(需要请求 URL;支持表单 payload 签名和 JSON `bodySHA256` 流程) | | **Svix** | `'svix'` | `svix-id`, `svix-timestamp`, `svix-signature` | | **Resend** | `'resend'` | 使用 Svix 签名 | | **Clerk** | `'clerk'` | 使用 Svix 签名 | | **WhatsApp / Meta** | `'meta'`, `'whatsapp'` | `x-hub-signature-256`(支持 `verifyMetaChallenge` GET 握手) | | **Discord** | `'discord'` | `x-signature-ed25519`, `x-signature-timestamp`(Ed25519 签名) | | **Linear** | `'linear'` | `linear-signature` | | **Razorpay** | `'razorpay'` | `x-razorpay-signature` | | **Square** | `'square'` | `x-square-hmacsha256-signature` | | **Zoom** | `'zoom'` | `x-zm-signature`, `x-zm-request-timestamp` | | **通用 / 自定义** | `'generic'` | 可配置的 header、算法、编码 | ## ⚡ CLI 模拟器 (`npx verihook simulate`) 在本地测试你的 webhook endpoint,**无需真实的 SaaS 账户或真实的 webhook**!CLI 会生成带有有效签名的 HMAC payload,并将其 POST 到你的服务器: ``` # 模拟 Stripe webhook npx verihook simulate stripe --url http://localhost:3000/webhooks/stripe # 模拟 GitHub issues 事件 npx verihook simulate github --event issues # 模拟 WhatsApp message webhook npx verihook simulate whatsapp --secret meta_app_secret_123 # 输出 cURL 命令而不是发送 POST npx verihook simulate stripe --curl ``` ## 快速开始 ### 基本用法 ``` import { verifyWebhook } from 'verihook'; const result = await verifyWebhook('stripe', req, process.env.STRIPE_WEBHOOK_SECRET!); if (result.valid) { console.log('Webhook verified! Timestamp:', result.timestamp); // Parse raw body string/Buffer to access event payload data const event = JSON.parse(req.body.toString('utf-8')); console.log('Event Type:', event.type); // e.g. "payment_intent.succeeded" console.log('Event Data:', event.data.object); // e.g. amount, customer ID, status } else { console.error(`Verification failed [${result.code}]:`, result.reason); } ``` ### 严格模式(抛出异常) ``` import { verifyWebhookOrThrow, WebhookVerificationError } from 'verihook'; try { await verifyWebhookOrThrow('github', req, process.env.GITHUB_WEBHOOK_SECRET!); // Process verified payload... } catch (err) { if (err instanceof WebhookVerificationError) { console.error(`[${err.provider}] Verification error (${err.code}):`, err.reason); } } ``` ### Provider 辅助函数 ``` import { verifyStripe, verifyGitHub, verifySlack, verifyWhatsApp, verifyDiscord } from 'verihook'; // Provider-specific shortcut functions await verifyStripe(req, process.env.STRIPE_SECRET!); await verifyGitHub(req, process.env.GITHUB_SECRET!); await verifySlack(req, process.env.SLACK_SECRET!); await verifyWhatsApp(req, process.env.META_APP_SECRET!); await verifyDiscord(req, process.env.DISCORD_PUBLIC_KEY!); ``` ### Meta / WhatsApp 验证握手 (`verifyMetaChallenge`) 在 Meta App Dashboard 中配置 webhook 时,Meta 需要进行一次 GET 挑战握手: ``` import { verifyMetaChallenge } from 'verihook'; // In your GET /webhooks/whatsapp handler: app.get('/webhooks/whatsapp', (req, res) => { const result = verifyMetaChallenge(req.query, process.env.META_VERIFY_TOKEN!); if (result.valid) { return res.status(200).send(result.challenge); } return res.status(403).send(result.reason); }); ``` ### 错误处理与错误代码 `verihook` 通过导出的 `WebhookErrorCode` 枚举提供结构化且类型安全的错误代码: ``` import { verifyWebhook, WebhookErrorCode } from 'verihook'; const result = await verifyWebhook('stripe', req, secret); if (!result.valid) { switch (result.code) { case WebhookErrorCode.INVALID_SIGNATURE: console.error('Signature mismatch — payload altered or secret incorrect'); break; case WebhookErrorCode.EXPIRED_TIMESTAMP: console.error('Timestamp outside allowed tolerance window'); break; case WebhookErrorCode.MISSING_HEADER: console.error('Required signature header missing'); break; case WebhookErrorCode.INVALID_BODY: console.error('Raw body missing — body was pre-parsed before verification'); break; } } ``` #### 可用的错误代码 | 错误代码 | 描述 | | :--- | :--- | | `WebhookErrorCode.INVALID_SIGNATURE` | HMAC 签名计算结果与传入的 header 不匹配。 | | `WebhookErrorCode.EXPIRED_TIMESTAMP` | Webhook 时间戳超出了容差窗口(默认 300 秒)。 | | `WebhookErrorCode.MISSING_HEADER` | 请求中缺少所需的 provider 签名 header。 | | `WebhookErrorCode.MISSING_URL` | 缺少请求 URL(Twilio / Square 必需)。 | | `WebhookErrorCode.INVALID_SECRET` | Webhook secret 为空或未提供。 | | `WebhookErrorCode.INVALID_BODY` | 传入了普通的 JS 对象,但未提供 `rawBody`。 | | `WebhookErrorCode.UNSUPPORTED_PROVIDER` | 无法识别的 provider 标识符。 | | `WebhookErrorCode.UNKNOWN_ERROR` | 处理过程中发生意外错误(原始错误已附加到 `result.error`)。 | ## 框架集成示例 ### Next.js App Router (Route Handler) ``` import { verifyWebhook } from 'verihook'; import { NextResponse } from 'next/server'; export async function POST(req: Request) { const result = await verifyWebhook('stripe', req, process.env.STRIPE_WEBHOOK_SECRET!); if (!result.valid) { return NextResponse.json({ error: result.reason }, { status: 401 }); } const payload = await req.json(); // Handle verified event... return NextResponse.json({ received: true }); } ``` ### Express.js ``` import express from 'express'; import { verifyWebhook } from 'verihook'; const app = express(); app.post('/api/webhook', express.raw({ type: 'application/json' }), async (req, res) => { const result = await verifyWebhook('github', req, process.env.GITHUB_SECRET!); if (!result.valid) { return res.status(401).json({ error: result.reason }); } // Parse raw body string/Buffer to process verified event data const payload = JSON.parse(req.body.toString('utf-8')); console.log('Verified Event Action:', payload.action); console.log('Verified Event Data:', payload.issue || payload.data?.object); res.status(200).json({ success: true }); }); ``` ### Hono / Cloudflare Workers ``` import { Hono } from 'hono'; import { verifyWebhook } from 'verihook'; const app = new Hono(); app.post('/webhook', async (c) => { const result = await verifyWebhook('shopify', c.req.raw, c.env.SHOPIFY_SECRET); if (!result.valid) { return c.json({ error: result.reason }, 401); } return c.json({ status: 'ok' }); }); export default app; ``` ## 选项与自定义 Provider ### 配置选项 ``` await verifyWebhook('stripe', req, secret, { tolerance: 600, // Customize maximum allowed timestamp drift in seconds (default: 300) now: Math.floor(Date.now() / 1000), // Override current timestamp for testing url: 'https://example.com/api/twilio', // Override URL for Twilio / Square }); ``` ### 自定义 HMAC 签名验证 (`'generic'`) ``` await verifyWebhook('generic', req, secret, { headerName: 'x-custom-signature', algorithm: 'sha256', // 'sha256' | 'sha1' | 'sha512' encoding: 'hex', // 'hex' | 'base64' | 'prefix-hex' }); ``` ### 注册自定义 Provider 插件 ``` import { registerProvider } from 'verihook'; registerProvider({ name: 'my-service', async verify(req, secret) { const signature = req.headers['x-myservice-sig']; // ... custom verification logic return { valid: true, provider: 'my-service' }; }, }); await verifyWebhook('my-service', req, secret); ``` ## License MIT © Piyush Anand
标签:MITM代理, TypeScript, Webhook, Zenmap, 力导向图, 安全插件, 开发库, 数据完整性, 程序员工具, 签名校验, 自动化攻击