Loading...
Loading...
Compare original and translation side by side
x-hookdeck-signaturex-hookdeck-signaturex-hookdeck-signaturex-hookdeck-signatureconst crypto = require('crypto');
function verifyHookdeckSignature(rawBody, signature, secret) {
if (!signature || !secret) return false;
const hash = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('base64');
try {
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hash));
} catch {
return false;
}
}const crypto = require('crypto');
function verifyHookdeckSignature(rawBody, signature, secret) {
if (!signature || !secret) return false;
const hash = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('base64');
try {
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hash));
} catch {
return false;
}
}import hmac
import hashlib
import base64
def verify_hookdeck_signature(raw_body: bytes, signature: str, secret: str) -> bool:
if not signature or not secret:
return False
expected = base64.b64encode(
hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
).decode()
return hmac.compare_digest(signature, expected)import hmac
import hashlib
import base64
def verify_hookdeck_signature(raw_body: bytes, signature: str, secret: str) -> bool:
if not signature or not secret:
return False
expected = base64.b64encode(
hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
).decode()
return hmac.compare_digest(signature, expected)undefinedundefinedundefinedundefinedconst express = require('express');
const app = express();
// IMPORTANT: Use express.raw() for signature verification
app.post('/webhooks',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-hookdeck-signature'];
if (!verifyHookdeckSignature(req.body, signature, process.env.HOOKDECK_WEBHOOK_SECRET)) {
console.error('Hookdeck signature verification failed');
return res.status(401).send('Invalid signature');
}
// Parse payload after verification
const payload = JSON.parse(req.body.toString());
// Handle the event (payload structure depends on original provider)
console.log('Event received:', payload.type || payload.topic || 'unknown');
// Return status code — Hookdeck retries on non-2xx
res.json({ received: true });
}
);const express = require('express');
const app = express();
// 重要:使用express.raw()进行签名验证
app.post('/webhooks',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-hookdeck-signature'];
if (!verifyHookdeckSignature(req.body, signature, process.env.HOOKDECK_WEBHOOK_SECRET)) {
console.error('Hookdeck签名验证失败');
return res.status(401).send('无效签名');
}
// 验证后解析负载
const payload = JSON.parse(req.body.toString());
// 处理事件(负载结构取决于原始服务商)
console.log('收到事件:', payload.type || payload.topic || '未知');
// 返回状态码 —— Hookdeck会对非2xx状态码进行重试
res.json({ received: true });
}
);import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
function verifyHookdeckSignature(body: string, signature: string | null, secret: string): boolean {
if (!signature || !secret) return false;
const hash = crypto.createHmac('sha256', secret).update(body).digest('base64');
try {
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hash));
} catch {
return false;
}
}
export async function POST(request: NextRequest) {
const body = await request.text();
const signature = request.headers.get('x-hookdeck-signature');
if (!verifyHookdeckSignature(body, signature, process.env.HOOKDECK_WEBHOOK_SECRET!)) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
}
const payload = JSON.parse(body);
console.log('Event received:', payload.type || payload.topic || 'unknown');
return NextResponse.json({ received: true });
}import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
function verifyHookdeckSignature(body: string, signature: string | null, secret: string): boolean {
if (!signature || !secret) return false;
const hash = crypto.createHmac('sha256', secret).update(body).digest('base64');
try {
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hash));
} catch {
return false;
}
}
export async function POST(request: NextRequest) {
const body = await request.text();
const signature = request.headers.get('x-hookdeck-signature');
if (!verifyHookdeckSignature(body, signature, process.env.HOOKDECK_WEBHOOK_SECRET!)) {
return NextResponse.json({ error: '无效签名' }, { status: 401 });
}
const payload = JSON.parse(body);
console.log('收到事件:', payload.type || payload.topic || '未知');
return NextResponse.json({ received: true });
}import os
import json
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
@app.post("/webhooks")
async def webhook(request: Request):
raw_body = await request.body()
signature = request.headers.get("x-hookdeck-signature")
if not verify_hookdeck_signature(raw_body, signature, os.environ["HOOKDECK_WEBHOOK_SECRET"]):
raise HTTPException(status_code=401, detail="Invalid signature")
payload = json.loads(raw_body)
print(f"Event received: {payload.get('type', 'unknown')}")
return {"received": True}For complete working examples with tests, see:
- examples/express/ - Full Express implementation with tests
- examples/nextjs/ - Next.js App Router implementation with tests
- examples/fastapi/ - Python FastAPI implementation with tests
import os
import json
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
@app.post("/webhooks")
async def webhook(request: Request):
raw_body = await request.body()
signature = request.headers.get("x-hookdeck-signature")
if not verify_hookdeck_signature(raw_body, signature, os.environ["HOOKDECK_WEBHOOK_SECRET"]):
raise HTTPException(status_code=401, detail="无效签名")
payload = json.loads(raw_body)
print(f"收到事件: {payload.get('type', '未知')}")
return {"received": True}如需完整的可运行示例及测试代码,请查看:
- examples/express/ - 完整的Express实现及测试
- examples/nextjs/ - Next.js App Router实现及测试
- examples/fastapi/ - Python FastAPI实现及测试
| Header | Description |
|---|---|
| HMAC SHA-256 signature (base64) — verify this |
| Unique event ID (use for idempotency) |
| Original request ID |
| Source that received the webhook |
| Destination receiving the webhook |
| Delivery attempt number |
| What triggered this attempt: |
| Seconds until next automatic retry (absent on last retry) |
| URL to view event in Hookdeck dashboard |
| Whether Hookdeck verified the original provider's signature |
| IP of the original webhook sender |
stripe-signaturex-hub-signature-256| 请求头 | 描述 |
|---|---|
| HMAC SHA-256签名(base64编码)—— 请验证该字段 |
| 唯一事件ID(用于实现幂等性) |
| 原始请求ID |
| 接收webhook的源名称 |
| 接收webhook的目标名称 |
| 递送尝试次数 |
| 触发本次尝试的原因: |
| 下次自动重试的间隔秒数(最后一次重试时不会包含该字段) |
| 在Hookdeck控制台查看事件的URL |
| Hookdeck是否已验证原始服务商的签名 |
| 原始webhook发送方的IP地址 |
stripe-signaturex-hub-signature-256.digest('base64').digest('hex')express.raw({ type: 'application/json' })crypto.timingSafeEqualhmac.compare_digestx-hookdeck-*.digest('base64').digest('hex')express.raw({ type: 'application/json' })crypto.timingSafeEqualhmac.compare_digestx-hookdeck-*undefinedundefinedundefinedundefined// Generated with: hookdeck-event-gateway-webhooks skill
// https://github.com/hookdeck/webhook-skills// Generated with: hookdeck-event-gateway-webhooks skill
// https://github.com/hookdeck/webhook-skills