RevoSurge 服务器事件 API 已弃用
将于 2026 年 10 月 18 日弃用
服务器事件 API v2 将于 2026 年 10 月 18 日弃用。请使用服务器事件 API (v3),并参阅从 v2 迁移。
受众: 工程师、技术集成方、管理员团队
概述
服务器事件 API 允许合作伙伴安全地将其服务器上的用户事件和用户活动直接发送到 DataPulse 平台。
本 API 面向高吞吐场景,需通过 API Key 进行严格认证。
Base URL
| 环境 | Base URL |
|---|---|
| Production | https://datapulse-api.revosurge.com/ |
认证
所有对服务器事件 API 的请求必须在请求头 X-API-Key 中包含 API Key。
必填请求头
| 请求头名称 | 说明 |
|---|---|
X-API-Key | 用于认证的 API Key |
接入端点
1. 单条事件接入
| 路径 | 方法 | Content-Type |
|---|---|---|
/v2/s2s/event | POST | application/json |
1.1 请求体 Schema
请求体接受包含以下字段的 JSON 对象:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
client_user_id | String | 是 | 您系统中用户的唯一标识符。 |
click_id | String | 是 | 广告点击的唯一 ID。 建议。 |
event_name | String | 是 | 事件名称(如 "register"、"login"、"deposit")。 |
timestamp | Integer | 是 | 事件发生的 Unix 时间戳(秒)。 |
ip_address | String | 是 | 用户的 IP 地址(IPv4/IPv6)。 |
user_agent | String | 否 | 浏览器或设备的 User Agent 字符串。 |
game_type | String | 否 | 游戏类型(如 "slot"、"casino")。 |
game_provider | String | 否 | 游戏提供商(如 "EA"、"GGP")。 |
transaction_id | String | 是* | 交易唯一 ID(如购买 ID)。 |
amount | Float | 是* | 交易金额(如充值金额)。 |
currency | String | 是* | 法币的 3 字母 ISO 货币代码(如 USD、EUR),或加密货币的任意代码。 |
is_crypto | Boolean | 否 | 若为加密货币交易则为 true,否则为 false。 |
<<any_prop>> | String | 否 | 其他自定义属性的键值对。 |
注:非财务事件(如 login)中 transaction_id、amount、currency 非必填。
1.2 示例请求 (curl)
curl -X POST "https://<<our-url>>/v2/s2s/event"
-H "Content-Type: application/json"
-H "X-API-KEY: dp_test_key_123"
-d '{
"client_user_id": "user_001",
"click_id": "clk_998877",
"ip_address": "203.0.113.1",
"event_name":"deposit",
"transaction_id": "tx_554433",
"timestamp": 1702963200,
"amount": 50.00,
"currency": "USD"
}'2. 批量事件接入
| 路径 | 方法 | Content-Type |
|---|---|---|
/v2/s2s/batch | POST | application/json |
2.1 请求体 Schema
请求体接受数组类型的 JSON 对象。数组项定义见 1.1 节。
响应
| 状态码 | 说明 |
|---|---|
| 200 OK | 事件已成功加入处理队列 |
| 400 Bad Request | 缺少必填字段 |
| 401 Unauthorized | API Key 无效或缺少认证头 |
| 429 Too Many Requests | 超出速率限制 |
| 500 Server Error | 内部处理错误。请使用退避策略重试 |
速率限制
- 限制按 X-API-KEY 应用。
- 标准限制:每分钟 60 次请求(默认)。
- 超出限制将返回 429 响应。
- 重试策略:建议在遇到 429 或 500 错误时实施指数退避策略。
Dry run
在任一接入端点上加 ?dryrun=1,即可端到端地检查一条请求而不产生任何数据。我们会完全按生产路径解析、鉴权、校验并富化它,把将要写入的那条记录原样回显,然后什么都不存。
| 端点 | Dry run URL |
|---|---|
| 单条 | POST /v2/s2s/event?dryrun=1 |
| 批量 | POST /v2/s2s/batch?dryrun=1 |
裸写 dryrun 与 dryrun=true 含义相同。dryrun=0、dryrun=false、dryrun=no 表示不启用。
如何把 dry run 与真实写入区分开
| 调用 | 状态码 | 响应体 | 响应头 |
|---|---|---|---|
| 真实写入 | 200 OK | { "status": "success" } | — |
| Dry run | 200 OK | 回显的那条记录 | X-Ingest-Mode: dryrun |
WARNING
两者都返回 200,所以单看状态码分不出你拿到的是哪一种——请看响应体和 X-Ingest-Mode: dryrun 响应头。dry run 通过不等于写入成功。 带 dryrun=1 发出的内容永远不会被存下来。
dry run 照常鉴权,也照常计入限流——一次批量请求算一次请求。它不是压测入口。
回显的那条记录
curl -X POST "https://<<our-url>>/v2/s2s/event?dryrun=1" \
-H "Content-Type: application/json" \
-H "X-API-KEY: dp_test_key_123" \
-d '{
"client_user_id": "u-ua-1",
"click_id": "rs-ua-1",
"event_name": "register",
"timestamp": 1702963200,
"ip_address": "203.0.113.1",
"user_agent": "Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36"
}'响应就是我们本来会存下的那条记录:
{
"requestId": "s2s.event-...",
"apiKey": "dp_test_key_...",
"clickId": "rs-ua-1",
"country": "USA",
"userAgentInfo": {
"device_class": "Mobile",
"device_name": "Samsung SM-G991B",
"operating_system_name": "Android",
"operating_system_version": "13",
"agent_name": "Android",
"agent_class": "Browser"
},
"eventData": {
"client_user_id": "u-ua-1",
"click_id": "rs-ua-1",
"event_name": "register",
"user_agent": "Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36",
"recv_timestamp": 1789541015965,
"request_id": "..."
},
"misspelledFields": []
}你发的 vs. 我们记的
你的载荷与回显之间的差异,大部分是我方补充的富化项:
| 你发的 | 我们记的 |
|---|---|
ip_address | 由该 IP 解析出的国家与城市 |
user_agent | userAgentInfo——设备类别与型号、操作系统名称与版本、客户端名称与类别 |
amount + currency | 按我方公布的汇率换算后的金额 |
game_type | 归一化后的游戏类型 |
transaction_id(你未提供时) | 由我方生成的 transaction_id |
| — | recv_timestamp——我方接收到该事件的时间,单位毫秒 |
| — | request_id / requestId——本次调用的追踪 id。就此次调用询问我们时请带上它 |
接下来两节是这张表的反面——哪些东西我们没有按你以为的样子记下来。
两种拼法
WARNING
绝对不要把回显里的字段名抄回你的请求里。 你发送的字段是 snake_case(client_user_id、click_id、ip_address、event_name、user_agent);回显的顶层字段是 camelCase(requestId、clickId、userAgentInfo),而嵌在其中的 eventData 又回到 snake_case。
拼错一个键的代价取决于该字段是否必填——而且损害程度与响度正好相反:
- 必填字段拼错返回
400。很响亮,几分钟就修好了。 - 可选字段拼错返回
200,事件照常落库,而那个字段静默消失。把user_agent写成userAgent,userAgentInfo整个为空——设备与操作系统维度全丢,而且哪里都不会报错。 click_id属于后一类。 拼成clickId会得到200、事件落库、看起来一切正常——只是这条转化永远归因不上。
misspelledFields
每一份 dry run 回显里都带有一个 misspelledFields 列表。它列出你发来的、明显是我方字段但拼法不同的键——正是它让上面那种静默失败在上线前变得可见:
"misspelledFields": [{ "sent": "userAgent", "expected": "user_agent" }]sent 是你原样发来的键名,expected 是我方本来会匹配到的字段。请把你的载荷改成 expected 的写法。
空数组就是你想要的答案。 单条请求干净通过时返回的是 "misspelledFields": []。这个键恒定存在,所以 [] 的含义是我们检查过了、没发现问题,而不是这个端点还没有这项检查。(批量的呈现方式不同,见下文。)
我们不拒绝未知字段
自定义属性是我方支持的能力,不是错误。 我方不认识的键会被原样收进事件属性里——1.1 节里 <<any_prop>> 那一行正是为此而设。
所以这项检查刻意做得很窄:它只报那些看起来是我方字段、只是拼法不同的键。比对前会先把名字统一成小写并去掉分隔符,只有这样处理之后与我方已知字段撞名的键才会被报出来。
| 你发的 | 是否报告 | 原因 |
|---|---|---|
userAgent、User-Agent | 报告 | 与 user_agent 撞名 |
CLIENTUSERID | 报告 | 与 client_user_id 撞名 |
user_agent | 不报告 | 拼写正确 |
bonus_round_id、vip_tier、table_id | 不报告 | 正当的自定义属性——不与我方任何字段撞名 |
IMPORTANT
你自己的自定义属性没有出现在这个列表里,正是应有的结果,并不代表它被忽略了。 它们会被原样记录下来。不要因为 dry run 没提到某个自定义属性,就把你本来要用的这个字段删掉。
批量 dry run
POST /v2/s2s/batch?dryrun=1 会同步返回逐条结果:
{
"requestId": "s2s.batch-...",
"mode": "dryrun",
"total": 3,
"successCount": 2,
"failureCount": 1,
"successEvents": ["... 富化后的事件 ..."],
"errors": [
{
"index": 2,
"violations": [
{ "field": "eventName", "rule": "required", "event": "deposit" },
{ "field": "clientUserId", "rule": "required", "event": "deposit" }
]
}
],
"misspelledFields": { "1": [{ "sent": "clickId", "expected": "click_id" }] }
}errors[].index是该条目在你发送的数组中的下标,从0数起;每个violations[]条目指出是哪个字段出了问题、违反了哪条规则——两者合起来告诉你第几条、哪个字段、违反哪条规则。misspelledFields是一个顶层对象,按同一套下标索引。 它不挂在successEvents里的各个事件上。上例是:第1条(也就是你发的第二条)把click_id拼成了clickId。- 全部拼对时这个键整个不出现——不是空对象,也不是空数组。这一点与单条不同:单条恒定返回
misspelledFields: []。所以不要拿"键是否存在"当判断依据。
NOTE
violations[].field 告诉你的是哪个字段出了问题,但它的写法不一定与你请求里的写法一致。请按请求侧的名字去修——请求字段是 snake_case(event_name、client_user_id)。
测试模式
dry run 回答的是"这一条对不对",什么都不留。测试模式回答的是"我的接入这段时间都发了什么":在任一接入端点上加请求头 X-Test-Mode: true,我们会完全按生产路径处理这条请求,然后把事件存成一条测试事件,你可以在 14 天内回头查看。
curl -i -X POST "https://<<our-url>>/v2/s2s/event" \
-H "Content-Type: application/json" \
-H "X-API-KEY: dp_test_key_123" \
-H "X-Test-Mode: true" \
-d '{
"client_user_id": "u-test-1",
"click_id": "rs-test-1",
"event_name": "register",
"timestamp": 1702963200,
"ip_address": "203.0.113.1"
}'| 调用 | 状态码 | 响应体 | 响应头 | 是否存储 |
|---|---|---|---|---|
| 真实写入 | 200 OK | { "status": "success" } | — | 是,进入生产 |
| 测试模式 | 200 OK | 回显的那条记录,或带 "mode": "test" 的批量逐条结果 | X-Ingest-Mode: test | 以测试事件形式保留 14 天 |
| Dry run | 200 OK | 回显的那条记录 | X-Ingest-Mode: dryrun | 否 |
- 测试事件永远不会进入生产。 不进任何报表,不会被归因到点击,不会被用于出价,也不会产生费用。14 天后删除。
- 同一条请求同时带
?dryrun=1和X-Test-Mode: true时,以 dry run 为准——什么都不会存下来。 - 校验不通过的测试事件会收到与生产相同的错误,不会被存下。批量请求中,只有通过校验的条目会被存下。
- 测试模式的请求照常计入限流。
如何查看测试事件以及相关限制,参见 v3 的测试模式——通过 v2 和 v3 发送的测试事件会列在一起。
请求体 Schema 使用场景
用户注册
{
"client_user_id": "<<THE UNIQUE USER ID>>",
"click_id": "<<THE UNIQUE CLICK ID>>",
"event_name": "register",
"timestamp": UTC seconds,
"ip_address": "<<THE END USER IP>>",
"user_agent": "<<THE USER AGENT STRING>>"
}用户登录
{
"client_user_id": "<<THE UNIQUE USER ID>>",
"click_id": "<<THE UNIQUE CLICK ID>>",
"event_name": "login",
"timestamp": UTC seconds,
"ip_address": "<<THE END USER IP>>",
"user_agent": "<<THE USER AGENT STRING>>"
}用户充值
{
"client_user_id": "<<THE UNIQUE USER ID>>",
"click_id": "<<THE UNIQUE CLICK ID>>",
"event_name": "deposit",
"currency": "<<THE CURRENCY, eg: USD | EUR | BTC>>",
"amount": 5.00,
"transaction_id": "<<THE UNIQUE TRANSACTION ID>>",
"timestamp": UTC seconds,
"ip_address": "<<THE END USER IP>>",
"is_crypto": true | false
}用户投注与输赢
投注的下注金额(bet)与结算结果(win / loss)需要分别上报。根据游戏的结算时机,请在以下两种方式中二选一上报结算结果,切勿同时使用,否则会导致重复计入。
- 方式 A — 合并上报(适用于即时结算游戏,如老虎机、轮盘):在
bet事件中直接带上bet_result与bet_result_amount,下注与结算一次性完成。 - 方式 B — 分开上报(适用于延迟结算游戏,如体育投注、扑克):先发送不带结算字段的
bet事件,待结算完成后再发送独立的win或loss事件,并通过parent_transaction_id关联到原bet事件的transaction_id。
方式 A:bet 事件合并上报结算结果
{
"client_user_id": "<<THE UNIQUE USER ID>>",
"event_name": "bet",
"game_type": "<<THE GAME TYPE>>",
"game_provider": "<<THE GAME PROVIDER>>",
"currency": "<<THE CURRENCY, eg: USD | EUR | BTC>>",
"amount": 5.00,
"transaction_id": "<<THE UNIQUE TRANSACTION ID>>",
"timestamp": UTC seconds,
"is_crypto": true | false,
"bet_result": "win | loss",
"bet_result_amount": 1.00
}注:amount 为玩家的下注金额;bet_result_amount 表示玩家的净输赢结果。当 bet_result 为 win 时填写正值;当 bet_result 为 loss 时填写负值(例如 -5.00)。
方式 B:独立的 win / loss 事件
先发送不含 bet_result / bet_result_amount 的 bet 事件(仅记录下注),结算后再发送对应的 win 或 loss 事件:
{
"client_user_id": "<<THE UNIQUE USER ID>>",
"event_name": "win | loss",
"game_type": "<<THE GAME TYPE>>",
"game_provider": "<<THE GAME PROVIDER>>",
"currency": "<<THE CURRENCY, eg: USD | EUR | BTC>>",
"amount": 5.00,
"transaction_id": "<<THE UNIQUE TRANSACTION ID>>",
"parent_transaction_id": "<<THE PARENT BET TRANSACTION ID>>",
"timestamp": UTC seconds,
"is_crypto": true | false
}注:amount 表示玩家的净输赢结果。当 event_name 为 win 时填写正值;当 event_name 为 loss 时填写负值(例如 -5.00)。parent_transaction_id 必须与对应 bet 事件的 transaction_id 一致,用于建立关联。
用户提现
{
"client_user_id": "<<THE UNIQUE USER ID>>",
"event_name": "withdraw",
"currency": "<<THE CURRENCY, eg: USD | EUR | BTC>>",
"amount": 5.00,
"transaction_id": "<<THE UNIQUE TRANSACTION ID>>",
"timestamp": UTC seconds,
"ip_address": "<<THE END USER IP>>",
"is_crypto": true | false
}