大使开发者
TANKA 大使 / API v1

归因与 Postback
服务端集成指南

面向接入方开发者的指南。介绍从分享链接、注册到首次成功完成 Link 的数据交互、认证方式和 API 行为。

BASE URLhttps://ambassador.tanka.aiproduction

集成概览#

有效用户:完成注册后,首次成功完成任意形式 Link 的用户。

免费用户也计入,付费不是条件。系统按上报的首次 Link 完成时刻匹配有效奖励规则。同一用户只产生一次推荐奖励。

  1. 01分享链接并保留来源

    管理员在启用大使时登记由本系统签发的推荐 URL 和推荐码。发送方保存触点与归因,需要时可查询历史映射。

  2. 02发送用户状态

    在注册、首次成功完成 Link、归因确认或更正后,发送完整用户状态。

  3. 03验证签名、计算奖励并对账

    接收内容持久化后异步处理,再与权威数据的完整快照对账。

服务端集成有两个入口:通过 GET /integration/v1/referral-links/{code} 查询推荐来源,通过 POST /integration/v1/events 发送用户状态。

凭据与认证#

Postback:Key ID + Secret

用接收方签发的 Key ID 标识密钥。Secret 保存在服务端,用于计算每个请求的 HMAC 签名。请求中发送的是签名,不能将 Secret 本身放入 header 或 body。

查询:专用 Bearer Token

推荐码查询使用专用 Token。定期获取补全 API 时,使用该 API 提供方另行签发的凭据。

发送服务端环境变量 · 示例
AMBASSADOR_API_BASE_URL=https://ambassador.tanka.ai
TANKA_EVENT_KEY_ID=<issued Key ID>
TANKA_EVENT_SECRET=<issued signing Secret>
TANKA_LINK_LOOKUP_TOKEN=<issued lookup Token>

使用集成专用凭据,无需接收方自身的平台管理密钥。不得把生产 Secret 放入文档、前端代码、分享链接或浏览器。

轮换密钥时,将发送方切换到新的 Key ID / Secret。接收方可在限定期限内兼容旧密钥。每次重试更新 timestamp 和签名,保留原 event_id 与业务数据 body。

发送完整用户状态#

POST/integration/v1/events

建议 Content-Type 为 application/json,UTF-8 body 上限为 65,536 字节。每次按固定字段结构发送完整快照,空值用 null 明示。不支持计数增量或局部 patch。

发送时机
字段填写说明
signup_completed注册正式完成时。first_link 和 first_link_completed_at 均为 null。仅注册不会产生推荐奖励。
first_link_completed注册后首次成功完成任意 Link 时。开始、处理中或失败均不计入;第二次及之后的 Link 不再产生推荐奖励。
attribution_updated归因更新时,例如确认此前待核查的来源。发送完整快照,并保留注册与首次 Link 的事实。
user_state_corrected更正、排除或恢复已记录事实时。使用新事件 ID、更高的用户状态版本,并提供 correction 依据。

以下 ID、时刻和 link_type 仅展示结构,使用时需替换为真实事件和登记信息。本页面不会自动向生产 API 发送示例。

首个 codeOnly.json 示例将三个内部 ID 全部设为 null。其他示例中的 ID 只是可选字段的填写演示,使用时必须换成真实查询结果。

按 referral_code 匹配:三个内部 ID 为 null
codeOnly.json
{
  "schema_version": "1.0",
  "event_id": "example.first-link.code-only",
  "event_type": "first_link_completed",
  "environment": "production",
  "program_id": "tanka-jp-ambassador",
  "user_id": "example-user-001",
  "state_version": 2,
  "occurred_at": "2026-09-08T00:05:00+09:00",
  "state_updated_at": "2026-09-08T00:05:00+09:00",
  "data": {
    "is_new_user": true,
    "registration_email": "[email protected]",
    "counting_status": "valid",
    "reason_code": null,
    "attribution": {
      "status": "attributed",
      "ambassador_id": null,
      "referral_code": "JP-example-only",
      "referral_link_id": null,
      "campaign_id": null,
      "touch_id": "example-touch-001",
      "touched_at": "2026-09-08T00:01:00+09:00",
      "bound_at": "2026-09-08T00:03:00+09:00",
      "model": "last_click",
      "window_days": 30,
      "rule_version": "attr-v1"
    },
    "milestones": {
      "signup_at": "2026-09-08T00:03:00+09:00",
      "first_link_completed_at": "2026-09-08T00:05:00+09:00"
    },
    "first_link": {
      "event_id": "example-link-success-001",
      "link_type": "calendar"
    },
    "qualification_rule_version": "first-link-v1",
    "correction": null
  }
}
注册完成:尚未完成首次 Link
signup.json
{
  "schema_version": "1.0",
  "event_id": "example.signup.001",
  "event_type": "signup_completed",
  "environment": "production",
  "program_id": "tanka-jp-ambassador",
  "user_id": "example-user-001",
  "state_version": 1,
  "occurred_at": "2026-09-08T00:03:00+09:00",
  "state_updated_at": "2026-09-08T00:03:00+09:00",
  "data": {
    "is_new_user": true,
    "registration_email": "[email protected]",
    "counting_status": "valid",
    "reason_code": null,
    "attribution": {
      "status": "attributed",
      "ambassador_id": "11111111-1111-4111-8111-111111111111",
      "referral_code": "JP-example-only",
      "referral_link_id": "22222222-2222-4222-8222-222222222222",
      "campaign_id": "33333333-3333-4333-8333-333333333333",
      "touch_id": "example-touch-001",
      "touched_at": "2026-09-08T00:01:00+09:00",
      "bound_at": "2026-09-08T00:03:00+09:00",
      "model": "last_click",
      "window_days": 30,
      "rule_version": "attr-v1"
    },
    "milestones": {
      "signup_at": "2026-09-08T00:03:00+09:00",
      "first_link_completed_at": null
    },
    "first_link": null,
    "qualification_rule_version": "first-link-v1",
    "correction": null
  }
}
首次 Link 完成:具备计算奖励所需的事实
firstLink.json
{
  "schema_version": "1.0",
  "event_id": "example.first-link.001",
  "event_type": "first_link_completed",
  "environment": "production",
  "program_id": "tanka-jp-ambassador",
  "user_id": "example-user-001",
  "state_version": 2,
  "occurred_at": "2026-09-08T00:05:00+09:00",
  "state_updated_at": "2026-09-08T00:05:00+09:00",
  "data": {
    "is_new_user": true,
    "registration_email": "[email protected]",
    "counting_status": "valid",
    "reason_code": null,
    "attribution": {
      "status": "attributed",
      "ambassador_id": "11111111-1111-4111-8111-111111111111",
      "referral_code": "JP-example-only",
      "referral_link_id": "22222222-2222-4222-8222-222222222222",
      "campaign_id": "33333333-3333-4333-8333-333333333333",
      "touch_id": "example-touch-001",
      "touched_at": "2026-09-08T00:01:00+09:00",
      "bound_at": "2026-09-08T00:03:00+09:00",
      "model": "last_click",
      "window_days": 30,
      "rule_version": "attr-v1"
    },
    "milestones": {
      "signup_at": "2026-09-08T00:03:00+09:00",
      "first_link_completed_at": "2026-09-08T00:05:00+09:00"
    },
    "first_link": {
      "event_id": "example-link-success-001",
      "link_type": "calendar"
    },
    "qualification_rule_version": "first-link-v1",
    "correction": null
  }
}
来源待核查:显式保留 null 字段
pendingSignup.json
{
  "schema_version": "1.0",
  "event_id": "example.signup.pending",
  "event_type": "signup_completed",
  "environment": "production",
  "program_id": "tanka-jp-ambassador",
  "user_id": "example-user-pending",
  "state_version": 1,
  "occurred_at": "2026-09-08T00:03:00+09:00",
  "state_updated_at": "2026-09-08T00:03:00+09:00",
  "data": {
    "is_new_user": true,
    "registration_email": "[email protected]",
    "counting_status": "hold",
    "reason_code": "attribution_unverified",
    "attribution": {
      "status": "pending",
      "ambassador_id": null,
      "referral_code": null,
      "referral_link_id": null,
      "campaign_id": null,
      "touch_id": null,
      "touched_at": null,
      "bound_at": null,
      "model": "last_click",
      "window_days": 30,
      "rule_version": "attr-v1"
    },
    "milestones": {
      "signup_at": "2026-09-08T00:03:00+09:00",
      "first_link_completed_at": null
    },
    "first_link": null,
    "qualification_rule_version": "first-link-v1",
    "correction": null
  }
}
后续确认待核查的来源
attributionUpdated.json
{
  "schema_version": "1.0",
  "event_id": "example.attribution.confirmed",
  "event_type": "attribution_updated",
  "environment": "production",
  "program_id": "tanka-jp-ambassador",
  "user_id": "example-user-pending",
  "state_version": 2,
  "occurred_at": "2026-09-08T00:04:00+09:00",
  "state_updated_at": "2026-09-08T00:04:00+09:00",
  "data": {
    "is_new_user": true,
    "registration_email": "[email protected]",
    "counting_status": "valid",
    "reason_code": null,
    "attribution": {
      "status": "attributed",
      "ambassador_id": "11111111-1111-4111-8111-111111111111",
      "referral_code": "JP-example-only",
      "referral_link_id": "22222222-2222-4222-8222-222222222222",
      "campaign_id": "33333333-3333-4333-8333-333333333333",
      "touch_id": "example-touch-001",
      "touched_at": "2026-09-08T00:01:00+09:00",
      "bound_at": "2026-09-08T00:04:00+09:00",
      "model": "last_click",
      "window_days": 30,
      "rule_version": "attr-v1"
    },
    "milestones": {
      "signup_at": "2026-09-08T00:03:00+09:00",
      "first_link_completed_at": null
    },
    "first_link": null,
    "qualification_rule_version": "first-link-v1",
    "correction": null
  }
}
更正:排除误计入的内部测试用户
corrected.json
{
  "schema_version": "1.0",
  "event_id": "example.correction.001",
  "event_type": "user_state_corrected",
  "environment": "production",
  "program_id": "tanka-jp-ambassador",
  "user_id": "example-user-001",
  "state_version": 3,
  "occurred_at": "2026-09-08T00:08:00+09:00",
  "state_updated_at": "2026-09-08T00:08:00+09:00",
  "data": {
    "is_new_user": true,
    "registration_email": "[email protected]",
    "counting_status": "invalid",
    "reason_code": "internal_test",
    "attribution": {
      "status": "attributed",
      "ambassador_id": "11111111-1111-4111-8111-111111111111",
      "referral_code": "JP-example-only",
      "referral_link_id": "22222222-2222-4222-8222-222222222222",
      "campaign_id": "33333333-3333-4333-8333-333333333333",
      "touch_id": "example-touch-001",
      "touched_at": "2026-09-08T00:01:00+09:00",
      "bound_at": "2026-09-08T00:03:00+09:00",
      "model": "last_click",
      "window_days": 30,
      "rule_version": "attr-v1"
    },
    "milestones": {
      "signup_at": "2026-09-08T00:03:00+09:00",
      "first_link_completed_at": "2026-09-08T00:05:00+09:00"
    },
    "first_link": {
      "event_id": "example-link-success-001",
      "link_type": "calendar"
    },
    "qualification_rule_version": "first-link-v1",
    "correction": {
      "reason_code": "internal_test",
      "reference_id": "example-audit-001",
      "supersedes_state_version": 2
    }
  }
}

字段与业务规则#

以下逐项列出事件 JSON 的全部字段,标明键是否必填、null 条件及数据来源。未定义的字段会被拒绝。类型、长度和基本格式可查看由同一校验定义生成的 OpenAPI JSON。

必填、可省略与 null 的区别

  • 必填:JSON 中必须出现该键。即使允许值为 null,也不能省略键本身。
  • 可省略:可以不发送该键。ambassador_id、referral_link_id、campaign_id 和 registration_email 可以省略,各字段的来源与 null 行为见下表。省略与 null 的签名字节不同,因此同一 event_id 重试时也不能改变可选键是否出现,否则返回 409。
  • null 是一个值。例如普通事件中的 {"reason_code": null} 合法,但删除 reason_code 键会被拒绝。标为“有条件”的 null 规则在对应行说明。
  • first_link 或 correction 为 null 时,不发送其子字段;传入对象时,对应表中的所有子字段均必填且不能为 null。

OpenAPI JSON

事件通用字段
字段发送要求数据来源填写说明
schema_version必填null: 不允许接入约定配置固定为 "1.0"。
event_id必填null: 不允许发送方生成并保存业务事件唯一且不可变的 ID。重试时保持不变,更正内容时使用新 ID。
event_type必填null: 不允许产品业务记录上述四种事件类型之一。
environment必填null: 不允许接入约定配置接受值为 production。本项目仅使用生产环境。
program_id必填null: 不允许接入约定配置固定值:tanka-jp-ambassador。
user_id必填null: 不允许产品业务记录TANKA 产品内不可变的用户 ID,不可用设备 ID、团队 ID 或邮箱代替。
state_version必填null: 不允许发送方生成并保存同一用户内单调递增的正整数,最大为 9007199254740991;每个用户可从 1 开始。
state_updated_at必填null: 不允许产品业务记录此完整状态的最后更新时间,必须不早于其中的注册与首次 Link 时刻。
occurred_at必填null: 不允许产品业务记录业务事件真实发生时刻,不是 HTTP 发送或重试时刻。
data必填null: 不允许发送方组装下述完整用户状态对象。
data:完整用户状态
字段发送要求数据来源填写说明
is_new_user必填null: 不允许产品业务记录表示是否为新注册用户的 boolean。已有用户不能设为 true。
registration_email可省略null: 允许产品业务记录TANKA 注册成功时使用的邮箱,最多254个字符。可省略或填 null,但能够取得时建议发送。每份完整状态和补全 feed 都应携带同一个注册时邮箱。仅在管理员的推荐用户列表展示。
counting_status必填null: 不允许发送方有效性判断valid / hold / invalid。即使为 valid,也须满足新用户、有效归因和首次成功 Link 的条件才计算奖励。
reason_code必填null: 有条件发送方有效性判断counting_status 为 hold / invalid 时必须有非空原因码;普通 valid 事件可填 null。
attribution必填null: 不允许发送方归因记录完整来源对象,字段见下表。
milestones必填null: 不允许产品业务记录包含注册与首次 Link 时间的对象,不能传空对象。
milestones.signup_at必填null: 不允许产品业务记录真实注册完成时刻。
milestones.first_link_completed_at必填null: 有条件产品业务记录首次 Link 成功前为 null;成功后填写实际首次成功时间,并同时提供 first_link 对象。重试或后续 Link 不改变此时间。
first_link必填null: 有条件产品业务记录首次 Link 成功前为 null;成功后必须提供包含 event_id 和 link_type 的对象,并同时填写 first_link_completed_at。
qualification_rule_version必填null: 不允许接入约定配置表示注册后首次成功完成任意 Link 的规则版本,必须与接收方配置值 first-link-v1 完全一致。查询响应中的 qualification_rule 也返回同一值。
correction必填null: 有条件发送方更正记录user_state_corrected 必须提供对象,其他普通事件可填 null。只要传入对象,下表列出的三个子字段均必填。
data.attribution:完整来源信息
字段发送要求数据来源填写说明
status必填null: 不允许发送方归因记录attributed / pending / unattributed。
ambassador_id可省略null: 允许接收方查询结果本系统的大使 ID。不需要时可省略或填 null;填写时须使用推荐码查询返回的 UUID。
referral_link_id可省略null: 允许接收方查询结果本系统登记的分享链接 ID。不需要时可省略或填 null;填写时须使用查询返回的 UUID。
campaign_id可省略null: 允许接收方查询结果本系统的活动 ID。不需要时可省略或填 null。填写的是本系统查询返回的 UUID,不是发送方广告平台的 campaign ID。
referral_code必填null: 有条件发送方归因记录由本系统签发、管理员登记的推荐码,是归因匹配的唯一键。attributed 时必须有值,其他状态未知时可填 null,长度1–64。
touch_id必填null: 有条件发送方归因记录发送方为点击生成并保存的标识。attributed 时必须有值,其他状态未知时可填 null。
touched_at必填null: 有条件发送方归因记录真实点击时间。attributed 时必须有值,其他状态未知时可填 null;填写时不得晚于注册时间。
bound_at必填null: 有条件发送方归因记录归因绑定或确认到注册用户的时间。attributed 时必须有值,其他状态未知时可填 null;填写时不得早于注册时间或晚于 state_updated_at。
model必填null: 不允许约定规则或查询结果适用的归因模型,必须与接收方配置值 last_click 完全一致。其他值会以 202 接收,但按 unknown_rule 保留。
window_days必填null: 不允许约定规则或查询结果适用的归因窗口天数,必须与接收方配置值 30 完全一致。
rule_version必填null: 不允许约定规则或查询结果适用的归因规则版本,必须与接收方配置值 attr-v1 完全一致。直接使用查询响应中的 rule_version。
data.first_link:传入对象时的子字段
字段发送要求数据来源填写说明
event_id父级为对象时必填null: 不允许产品业务记录产品内标识首次 Link 成功完成的真实事件 ID,与外层用于回传的 event_id 用途不同。
link_type父级为对象时必填null: 不允许产品业务记录首次成功完成的实际 Link 类型,不能猜测或传空字符串。
data.correction:传入对象时的子字段
字段发送要求数据来源填写说明
reason_code父级为对象时必填null: 不允许发送方更正记录说明更正原因的非空代码。
reference_id父级为对象时必填null: 不允许发送方更正记录可追溯更正依据的 ID,例如审计或操作记录编号。
supersedes_state_version父级为对象时必填null: 不允许发送方更正记录被更正的用户状态版本,必须是小于本次 state_version 的正整数。
  • attributed 时,referral_code、touch_id、touched_at、bound_at 必填。ambassador_id、referral_link_id、campaign_id 可省略或为 null。
  • first_link 与 first_link_completed_at 必须同时有值或同时为 null。首次 Link 时刻不得早于注册时刻。
  • 时刻使用带明确偏移的 RFC3339。T 和 Z 大写,偏移带冒号(例如 +09:00),小数秒最多 6 位。+0900、+09、小写 z 以及 7 位及以上小数秒会返回 400。跨字段比较(touched_at ≤ signup_at ≤ bound_at ≤ state_updated_at,state_updated_at ≥ 首次 Link)按秒精度进行,同一事件内精度不一致也可接受。state_updated_at 按用户单调不减;比已保存状态更早的值即使带 correction 也会被隔离。occurred_at 和 state_updated_at 最多可比接收方当前时刻晚 300 秒。补全可发送真实的历史业务时刻。
  • 所有 ID 类字段使用同一字符集。event_id、user_id、program_id、touch_id、referral_link_id、ambassador_id、campaign_id、rule_version、qualification_rule_version、first_link.event_id、correction.reference_id 只允许英文字母、数字、下划线、句点、冒号或连字符([A-Za-z0-9_.:-]),长度 1–160,区分大小写。含 |、@、/ 或空格的值会返回 400。referral_code 使用相同字符集,长度 1–64。可选内部 ID 必须使用查询获得的真实 UUID。
  • 首次提供的真实 registration_email 会被保存。普通事件省略或填 null 不会清除已知的注册时邮箱。更改已知值需要更高状态版本和 correction;在更正中明确将这个键设为 null,可以清除错误记录的邮箱。普通(非更正)事件中不同的邮箱值将被忽略,保留首次记录的值。用户后来修改账号邮箱,不改变注册时记录。同一用户、同一 state_version 在 Webhook 与补全 feed 中的 data 必须字节级一致。仅时区写法不同(+09:00 与 Z)或可选键出现与否不同,feed 侧状态就会以 VERSION_CONFLICT 隔离。
  • registration_email 用于管理员核对推荐转化,不向大使返回。用户身份和奖励去重仍使用 user_id。姓名、银行信息、付款金额和订阅信息属于未定义字段,会被拒绝。奖励单价按业务时刻确定。

生成签名并发送请求#

X-Tanka-Key-Id、X-Tanka-Timestamp、X-Tanka-Signature 三个 header 必填。Content-Type 建议使用 application/json。接收方读取的是 body 原始字节而非声明的类型,并对这些字节验证签名。请设置产品化的 User-Agent(例如 ambassador-postback/1.0):边缘网络会以 HTTP 403 拒绝个别库的默认 UA(例如 Python-urllib/3.x),该请求不会到达接收方。

Postback 请求 header
字段填写说明
Content-Typeapplication/json
X-Tanka-Key-Id填写另行收到的 Key ID,不填写 Secret。
X-Tanka-Timestamp发送时刻的 Unix 秒数,以整数字符串表示,不使用毫秒。
X-Tanka-Signature64 个小写十六进制字符 HMAC-SHA256 摘要,不加 sha256= 等前缀。
签名计算公式
message = ASCII(timestamp) + ASCII(".") + raw_body_bytes
signature = lowercase_hex(HMAC_SHA256(UTF8(secret), message))

原样发送参与签名的字节。签名后不得改变 JSON 格式、换行、字段顺序或字符编码。允许时钟偏差为 300 秒,需保持服务端时钟同步。HTTPS 保护传输,HMAC 验证密钥持有权与 body 完整性。系统按事件去重。

Node.js · 签名并原样发送文件字节
postback.mjs
// Node.js 20+ / ESM. Run: node postback.mjs approved-real-event.json
import { readFileSync } from "node:fs";
import { createHmac } from "node:crypto";

function required(name) {
  const value = process.env[name];
  if (!value) throw new Error("Missing environment variable: " + name);
  return value;
}
if (!process.argv[2]) throw new Error("Pass the path to a real event JSON file");
const url = new URL("/integration/v1/events", required("AMBASSADOR_API_BASE_URL"));
if (url.protocol !== "https:") throw new Error("HTTPS is required");
const keyId = required("TANKA_EVENT_KEY_ID");
const secret = required("TANKA_EVENT_SECRET");
const body = readFileSync(process.argv[2]);
JSON.parse(body.toString("utf8")); // Keep the ORIGINAL bytes for signing and sending.
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = createHmac("sha256", secret)
  .update(timestamp).update(".").update(body).digest("hex");

const response = await fetch(url, {
  method: "POST",
  redirect: "error",
  signal: AbortSignal.timeout(15000),
  headers: {
    "Content-Type": "application/json",
    // Send an explicit product User-Agent: the edge blocks a few generic library agents (e.g. Python-urllib).
    "User-Agent": "ambassador-postback/1.0",
    "X-Tanka-Key-Id": keyId,
    "X-Tanka-Timestamp": timestamp,
    "X-Tanka-Signature": signature,
  },
  body,
});
console.log("HTTP " + response.status + " " + (await response.text()).slice(0, 4096));
if (![200, 202].includes(response.status)) process.exitCode = 1;
// Queue retries outside this example. Keep event_id/body, refresh timestamp/signature.
Python · 用标准库生成相同签名
postback.py
# Python 3.10+, standard library. Run: python3 postback.py approved-real-event.json
import hashlib
import hmac
import json
import os
from pathlib import Path
import sys
import time
import urllib.error
import urllib.parse
import urllib.request

def required(name):
    value = os.environ.get(name)
    if not value:
        raise SystemExit("Missing environment variable: " + name)
    return value

if len(sys.argv) != 2:
    raise SystemExit("Pass the path to a real event JSON file")
url = urllib.parse.urljoin(required("AMBASSADOR_API_BASE_URL"), "/integration/v1/events")
if urllib.parse.urlparse(url).scheme != "https":
    raise SystemExit("HTTPS is required")
key_id = required("TANKA_EVENT_KEY_ID")
secret = required("TANKA_EVENT_SECRET")
body = Path(sys.argv[1]).read_bytes()
json.loads(body)  # Keep the ORIGINAL bytes for signing and sending.
timestamp = str(int(time.time()))
signature = hmac.new(secret.encode("utf-8"), timestamp.encode("ascii") + b"." + body,
                     hashlib.sha256).hexdigest()

class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None

request = urllib.request.Request(url, data=body, method="POST", headers={
    "Content-Type": "application/json",
    # Explicit product User-Agent: the edge rejects urllib's default "Python-urllib/x.y" with HTTP 403.
    "User-Agent": "ambassador-postback/1.0",
    "X-Tanka-Key-Id": key_id,
    "X-Tanka-Timestamp": timestamp,
    "X-Tanka-Signature": signature,
})
opener = urllib.request.build_opener(NoRedirect())
try:
    response = opener.open(request, timeout=15)
except urllib.error.HTTPError as error:
    response = error
with response:
    status = response.code
    print("HTTP", status, response.read(4096).decode("utf-8", errors="replace"))
if status not in (200, 202):
    raise SystemExit(1)
# Queue retries outside this example. Keep event_id/body, refresh timestamp/signature.

示例从文件读取 body、从服务端环境变量读取凭据,仅发送一次。生产发送方需实现持久队列、失败通知和重试。OpenAPI 认证定义仅描述 header,签名逻辑由发送方实现。

响应、重试与更正#

按 HTTP 状态与 error 处理
字段填写说明
202 · accepted{event_id,status:"accepted"}:接收内容已持久化,可视为发送完成;奖励异步处理,未必已计入。
200 · duplicate{event_id,status:"duplicate"}:已接收相同内容的同一事件,停止重试。
400 · INVALID_INPUTissues 的每一项包含违规字段的 path 和以英文说明具体规则的 message。跨字段规则(例如时刻先后关系)的 path 为空字符串。按 message 修正输入后重试。请求支持时附上接收方签发的 request_id。
401 · UNAUTHORIZED响应仅包含 error: UNAUTHORIZED。检查 Key ID、发送时刻、原始 body 和签名;暂停重试,排查凭据与服务端时钟。
409 · EVENT_ID_CONFLICT同一事件 ID 的内容发生了变化,请检查发送记录。使用不同 body 重试不能覆盖已有事件。
413 · INVALID_INPUTbody 超过上限,需调整事件内容并移除不必要的数据。
422 · ENVIRONMENT_MISMATCH / FUTURE_EVENT检查生产环境与计划 ID,或修正错误的未来业务时刻。
503 / 其他 5xx / 超时可能尚未接收,也可能已接收但响应丢失。保留 event_id 和 body,更新 timestamp 与签名,采用退避和随机抖动后重试。
429(由边缘服务返回时)接收 API 本身未定义 429。若上游返回该状态,有 Retry-After 时遵循其要求,等待后重试。

发送方先保存业务数据和发送任务,再后台投递。重试间隔逐步延长,例如 5 秒、30 秒、2 分钟、10 分钟,并加入随机抖动。超期失败移入待核查队列。不能仅因超时就生成新事件 ID。

同一用户的旧版本不会覆盖新状态。同版本内容不一致时,会在异步处理中隔离。HTTP 202 不保证对账一致。新的事件 ID 也不会为同一用户重复计奖。更正需使用新的 event_id、更大的 state_version 和可追溯依据的 correction。已确认明细可能通过后续差额条目调整。归因在注册时即固定。不带更正的普通事件若把已归属用户改为 pending / unattributed,或更改 referral_code,会以 FACT_CONFLICT 隔离。

补全与对账 API#

大使系统定期通过 GET 拉取接入方 API。接收方会配置您提供的完整拉取 URL 与专用 Bearer Token。

请求方向:大使系统 → 接入方
GET <partner feed URL>?program_id=tanka-jp-ambassador&limit=500&cursor=<next cursor>
Authorization: Bearer <backfill Token>
对账 API 分页响应
{
  "items": [
    {
      "schema_version": "1.0",
      "event_id": "example.first-link.001",
      "event_type": "first_link_completed",
      "environment": "production",
      "program_id": "tanka-jp-ambassador",
      "user_id": "example-user-001",
      "state_version": 2,
      "occurred_at": "2026-09-08T00:05:00+09:00",
      "state_updated_at": "2026-09-08T00:05:00+09:00",
      "data": {
        "is_new_user": true,
        "registration_email": "[email protected]",
        "counting_status": "valid",
        "reason_code": null,
        "attribution": {
          "status": "attributed",
          "ambassador_id": "11111111-1111-4111-8111-111111111111",
          "referral_code": "JP-example-only",
          "referral_link_id": "22222222-2222-4222-8222-222222222222",
          "campaign_id": "33333333-3333-4333-8333-333333333333",
          "touch_id": "example-touch-001",
          "touched_at": "2026-09-08T00:01:00+09:00",
          "bound_at": "2026-09-08T00:03:00+09:00",
          "model": "last_click",
          "window_days": 30,
          "rule_version": "attr-v1"
        },
        "milestones": {
          "signup_at": "2026-09-08T00:03:00+09:00",
          "first_link_completed_at": "2026-09-08T00:05:00+09:00"
        },
        "first_link": {
          "event_id": "example-link-success-001",
          "link_type": "calendar"
        },
        "qualification_rule_version": "first-link-v1",
        "correction": null
      }
    }
  ],
  "next_cursor": null,
  "watermark": "2026-09-08T00:06:00+09:00",
  "snapshot_id": "example-snapshot-001",
  "complete": true
}
  • 返回截至 watermark 时点、与本项目相关的全部用户完整状态,包括待归因、无效、撤销和更正。仅汇总人数或仅有效用户列表无法用于对账。曾经回传过的每个 user_id 都必须持续出现在 feed 中。删除或合并的用户以 counting_status=invalid 的更正表达。缺少已知用户会导致 RECONCILE_DIFFERENCE,月度确认随之阻塞。
  • 每页 items 最多 500 条。返回完整事件,或包含 environment / program_id / user_id / state_version / state_updated_at / data 的完整用户状态。只要有一条不合法(未定义键、environment 或 program_id 不符、业务时刻比接收方当前时刻超出容差地领先),整轮对账即以 FEED_ITEM_REJECTED 失败,并从快照起点重试。
  • 以 next_cursor=null 表示结束。最后一页也不能省略 next_cursor 键,必须显式返回 null(缺键则整页失败)。首次请求省略 cursor,后续页原样传入返回的游标。再次返回相同的游标值会以 REPEATED_CURSOR 失败。空页不能代替结束标记。
  • 获取同一快照期间保持 watermark 和快照标识不变。所有状态更新时间不得晚于 watermark,避免翻页期间的数据变动造成遗漏。
  • 分页 envelope 严格校验。顶层只允许 items、next_cursor、watermark、snapshot_id、complete 五个键,出现任何其他键都会使整页失败。snapshot_id 可省略,此时用 watermark 标识快照。complete 也可省略;传入时须与是否为最后一页一致。
  • 每页上限 4 MiB,最多 2,000 页。达到页数上限后仍有下一页时,以 FEED_TOO_LONG 失败。请求超时为 30 秒,不跟随重定向。失败后从快照首页重新拉取,已接收数据会去重。
  • 月度确认要求最近 24 小时内完成一次完整对账,且 watermark 覆盖上月末。普通 CSV/JSON 导入不能作为完整性证明。