主题
服务端对接
文档版本 v1.0.0
本页对应 SDK 1.0.0,与离线包 docs/ 目录里的同一份文档内容一致。其他版本见文档中心。
目前只有一项服务端对接能力:激励视频服务端发奖验证。
什么时候需要
onReward 发生在用户手机上,可被 Hook、重放伪造。奖励能提现、抵扣、抽奖, 或者有人会为它刷量时,都应当接服务端验证。
奖励只是「多一次复活」这类无价值的,只用 onReward 即可,本文可以跳过。
链路
用户看完广告 → 穿山甲(GroMore)/ 优量汇服务器通知 AdMix 平台 → 平台用广告平台的密钥验签, 按平台广告位找到对应应用 → 用该应用的 AppSecret 签名 → POST 到接入方配置的接收地址 → 接入方服务器验签后给用户发奖。
第一步:配置接收地址
管理后台以「开发者」身份登录 →「我的应用」→ 应用详情 →「服务端发奖验证」,填写 https 公网地址。 页面下方可以看到每条通知的发送状态与明细。
AppSecret 由平台运营单独交付,只显示一次。
AppSecret 绝不能进 APK
它只用于接入方服务器与平台之间的验签,进了 APK 等于公开。 SDK 没有任何接收 AppSecret 的入口 —— 看到哪份文档要求把它填进客户端,那份文档是错的。
第二步:请求时带上用户 ID
java
RewardAd rewardAd = new RewardAd("reward_main")
// 用户 ID、自定义数据,均可传 null;必须在 load 之前调用,对之后每次 load 都生效
.setServerSideVerification(currentUser.getId(), "scene=daily_sign");
rewardAd.load(activity, listener);- 用户 ID 用接入方自己系统里的用户标识,不要放手机号、身份证号; 自定义数据原样带回,不超过 1024 个字符
- SDK 分别透传给穿山甲 GroMore(
AdSlot.setUserID+MediationConstant.CUSTOM_DATA_KEY_GROMORE_EXTRA)和优量汇(ServerSideVerificationOptions), 接入方不需要关心 - 带了这两个参数的请求不使用预加载缓存(缓存里的广告请求时没有这次的用户 ID)
- 不调用时,请求与不接服务端验证完全一致
- 日志里能看到
[CSJ激励] 服务端验证参数已设置 userId=..., 排查「通知里没有用户 ID」时先看这一行
第三步:接入方服务器验签
请求为 POST,Content-Type: application/json。
请求头
| 请求头 | 含义 |
|---|---|
X-Admix-App-Id | AppID |
X-Admix-Event-Id | 通知 ID,与请求体里 event_id 相同 |
X-Admix-Timestamp | 秒级时间戳 |
X-Admix-Nonce | 随机串 |
X-Admix-Signature | 签名,小写 hex |
text
signature = hex( HMAC-SHA256( AppSecret, app_id + "\n" + timestamp + "\n" + nonce + "\n" + 请求体原始字节 ) )请求体
json
{
"event_id": "rw_3f2a…",
"app_id": "应用的 AppID",
"unit_id": "reward_main",
"adn": "csj",
"trans_id": "…",
"user_id": "10086",
"custom_data": "scene=daily_sign",
"reward_name": "金币",
"reward_amount": 10,
"verified_at": 1789612473
}| 字段 | 说明 |
|---|---|
event_id | 通知 ID,全局唯一。平台重试、人工重推都不变,按它去重 |
app_id | AppID |
unit_id | 广告位标识 |
adn | 广告平台标识,csj = 穿山甲,gdt = 优量汇 |
trans_id | 广告平台的交易 ID |
user_id | setServerSideVerification 传入的用户 ID |
custom_data | setServerSideVerification 传入的自定义数据,原样带回 |
reward_name | 广告平台后台配置的奖励名称,优量汇为空串 |
reward_amount | 奖励数量,优量汇为 null |
verified_at | 平台验签通过的时间,秒级时间戳 |
验签规则
- 用收到的原始字节计算签名(不要解析 JSON 后再序列化),与
X-Admix-Signature做常量时间比较,不一致返回 401 timestamp与本机时间相差超过 5 分钟拒绝;nonce10 分钟内重复拒绝- 核对
app_id是自己的应用;按event_id去重,处理过的直接返回 200 - 给
user_id发奖,事务提交后返回 HTTP 2xx
重试策略
其他状态码或 5 秒超时视为失败,平台按 15 秒、1 分钟、5 分钟、15 分钟、1 小时、3 小时、6 小时 后重试(共 8 次),用尽后可请平台运营人工重推。不跟随重定向。
示例
Java(JDK 17+):
java
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(APP_SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
mac.update((appId + "\n" + ts + "\n" + nonce + "\n").getBytes(StandardCharsets.UTF_8));
String expected = HexFormat.of().formatHex(mac.doFinal(rawBody));
boolean ok = MessageDigest.isEqual(expected.getBytes(StandardCharsets.UTF_8),
signature.toLowerCase().getBytes(StandardCharsets.UTF_8));Python:
python
expected = hmac.new(APP_SECRET.encode(), f"{app_id}\n{ts}\n{nonce}\n".encode() + raw_body, hashlib.sha256).hexdigest()
ok = hmac.compare_digest(expected, signature.lower())含时间戳校验、nonce 去重、按 event_id 幂等发奖的完整 Spring Boot / Flask 示例见管理后台 「接入指引 → 激励视频服务端发奖验证」。