接入流程
- GEO 在本地 SQLite 中为每次访问生成
click_id,但不会把它附加到目标 URL。 - 业务方如通过其他受控机制取得该值,可沿用到注册、充值和支付记录。
- 业务事件完成后,使用双方共享的签名密钥调用 Webhook。
- 收到
202或200即可停止重试。
未知但格式正确的 click_id 仍会接收,响应中的 matched_click 为 false,便于排查归因链路。
请求字段
| 字段 | 类型 | 说明 |
|---|---|---|
event_id | string | 事件唯一 ID,最长 128 字符;用于幂等。 |
event_type | enum | user.registered、wallet.recharged 或 order.paid。 |
occurred_at | date-time | 事件实际发生时间,必须包含时区。 |
click_id | UUID | GEO 本地访问记录中的归因 ID;不会通过目标 URL 传递。 |
user_id | string | 外部业务系统用户 ID,最长 128 字符。 |
data | object | 可选业务字段,例如订单号、金额和币种;不得放入密码或访问令牌。 |
{
"event_id": "evt_01J5EXAMPLE",
"event_type": "user.registered",
"occurred_at": "2026-08-20T10:00:00.000Z",
"click_id": "0f62d3c3-38c2-4db5-8451-8db1cabb2f0d",
"user_id": "user_12345",
"data": { "registration_source": "web" }
}
签名规则
请求必须包含 X-GEO-Timestamp 和 X-GEO-Signature。签名输入是时间戳、英文句点和未经重新格式化的原始请求体:
signed_payload = timestamp + "." + raw_body signature = "v1=" + hex(HMAC-SHA256(WEBHOOK_SIGNING_SECRET, signed_payload))
服务采用常量时间比较,并拒绝与服务器时间相差超过 300 秒的请求。
Node.js 调用示例
import { createHmac } from 'node:crypto';
const secret = process.env.WEBHOOK_SIGNING_SECRET;
const timestamp = Math.floor(Date.now() / 1000).toString();
const body = JSON.stringify(event);
const signature = 'v1=' + createHmac('sha256', secret)
.update(timestamp + '.')
.update(body)
.digest('hex');
const webhookUrl = new URL('/webhooks/events', process.env.GEO_BASE_URL);
const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GEO-Timestamp': timestamp,
'X-GEO-Signature': signature
},
body
});
响应与重试
{ "status": "accepted", "event_id": "evt_01J5EXAMPLE", "matched_click": true }
| 状态 | 含义与处理 |
|---|---|
202 | 新事件已保存,不要重试。 |
200 | 同一事件已保存,幂等成功,不要重试。 |
400/401/409/413/415/426 | 请求需要修正,不应原样重试。 |
5xx | 使用指数退避重试;复用同一个 event_id。 |
同一个 event_id 携带不同内容会返回 409。请勿为重试生成新的事件 ID。
安全约束
- 生产环境仅接受 HTTPS。
- 签名密钥至少 32 字符,不得与管理员密码或访客 HMAC 密钥复用。
- 请求体最大 64 KiB,顶层不接受未定义字段。
- 服务不会记录原始 IP;事件数据写入同一 SQLite 持久化卷。