跳转到内容

签名

每个回调请求都带有以下请求头:

Content-Type: application/json
User-Agent: kasskeep-webhook/1
Kasskeep-Signature: t=1767225600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Kasskeep-Event-Id: evt_...
Kasskeep-Event-Type: purchase
v1 = hex( HMAC-SHA256( webhook_secret, t + "." + raw_body ) )
  • t 是签名时的 Unix 时间,单位秒。
  • raw_body 是请求体的原始字节。校验前不要先解析再重新序列化 JSON。
  • 密钥是完整的 webhook_secret 字符串(包括 whsec_ 前缀),按 UTF-8 编码。
  • t 与你的时钟相差超过 300 秒的请求要拒绝。
  • 用常量时间比较。

广告奖励事件和测试事件也使用同一套方案签名。

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 = 1767225600
body = {"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=28b67105363ce706a96273806aead08f023d7ee0943f554d24c23092a285a098

2. 请求体含非 ASCII 字符(请求体按 UTF-8 字节计算哈希)

t = 1767225600
body = {"id":"evt_3f0c","type":"purchase","price_micros":9990000,"currency":"USD","data":{"note":"café ✓"}}
Kasskeep-Signature: t=1767225600,v1=0f8440805bf6acbead256925900012c74620ca8cadc4476de63a13ba2cb20015

3. 空请求体

t = 1767225601
body = (empty)
Kasskeep-Signature: t=1767225601,v1=c926a366d97dd550371b5547522a89745af722d51d02bc155fb9f048de48f81b

4. 必须校验失败:向量 1 的请求头,但请求体任意改动一个字节,或把 t 改成 1767225601。

这些向量由 Kasskeep 投递回调时所用的同一个签名函数生成。