签名
每个回调请求都带有以下请求头:
Content-Type: application/jsonUser-Agent: kasskeep-webhook/1Kasskeep-Signature: t=1767225600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdKasskeep-Event-Id: evt_...Kasskeep-Event-Type: purchasev1 = hex( HMAC-SHA256( webhook_secret, t + "." + raw_body ) )t是签名时的 Unix 时间,单位秒。raw_body是请求体的原始字节。校验前不要先解析再重新序列化 JSON。- 密钥是完整的
webhook_secret字符串(包括whsec_前缀),按 UTF-8 编码。 t与你的时钟相差超过 300 秒的请求要拒绝。- 用常量时间比较。
Node.js
Section titled “Node.js”import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyKasskeepSignature(secret, header, rawBody, toleranceSeconds = 300) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2))); const t = Number(parts.t); if (!Number.isFinite(t) || !parts.v1) return false; if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false; const expected = createHmac("sha256", secret).update(`${parts.t}.`).update(rawBody).digest(); const actual = Buffer.from(parts.v1, "hex"); return actual.length === expected.length && timingSafeEqual(actual, expected);}rawBody 可以是 Buffer 或字符串。用 Express 时,在回调路由上使用 express.raw({ type: "application/json" }),避免请求体被提前解析。
所有向量都使用这个密钥:
whsec_kasskeep_test_vector_secret_0001校验时请关闭时间容差检查,因为这些时间戳都在过去。
1. 一个测试事件
t = 1767225600body = {"id":"evt_00000000000000000000000000000001","type":"test","created_at":"2026-01-01T00:00:00.000Z","app_user_id":null,"platform":null,"environment":null,"product_id":null,"transaction_id":null,"original_transaction_id":null,"purchased_at":null,"expires_at":null,"price_micros":null,"currency":null,"ownership":null,"data":{"test":true}}Kasskeep-Signature: t=1767225600,v1=28b67105363ce706a96273806aead08f023d7ee0943f554d24c23092a285a0982. 请求体含非 ASCII 字符(请求体按 UTF-8 字节计算哈希)
t = 1767225600body = {"id":"evt_3f0c","type":"purchase","price_micros":9990000,"currency":"USD","data":{"note":"café ✓"}}Kasskeep-Signature: t=1767225600,v1=0f8440805bf6acbead256925900012c74620ca8cadc4476de63a13ba2cb200153. 空请求体
t = 1767225601body = (empty)Kasskeep-Signature: t=1767225601,v1=c926a366d97dd550371b5547522a89745af722d51d02bc155fb9f048de48f81b4. 必须校验失败:向量 1 的请求头,但请求体任意改动一个字节,或把 t 改成 1767225601。
这些向量由 Kasskeep 投递回调时所用的同一个签名函数生成。