# GET /api/v1/ad/pangle-callback — 穿山甲 GroMore 激励视频发奖回调(S2S)

> 所属:Ad 组（前缀 `/api/v1/ad`） | 鉴权:**无 JWT,靠验签**（穿山甲 GroMore 服务器调用） | 限流:同 IP ≤300 次/分 | [← 返回 API 索引](../README.md)
>
> ⚠️ 我们客户端用 `useMediation(true)`(GroMore 融合),回调走 **GroMore 广告位层级**(规范见 supportcenter/26240),**不是**联盟代码位层级(5416)。两者密钥、响应格式都不同,别混。后台配置入口:**GroMore 聚合管理 → 搜广告位ID → 编辑 → 勾选「服务端激励回调」**(广告位层级配了就别再在代码位层级重复配,会冲突)。
>
> 集成实现:见 [integrations/pangle](../integrations/pangle.md)（验签算法、m-key 来源、设计动机）。

## 入参（query，由 GroMore 拼装）
GroMore 以 GET 回调,关键参数:

| 字段 | 类型 | 说明 |
|---|---|---|
| `user_id` | string | 客户端 `setUserID` 传入的用户标识（须为数字 = 本系统 user.id） |
| `trans_id` | string | 交易号（**幂等键** + **唯一参与签名的字段**） |
| `reward_name` | string | 奖励名（广告位配置,入库备注） |
| `ecpm` | string\|null | GroMore 回调携带的 eCPM。普通激励视频优先用它计算金币 |
| `extra` / `gromoreExtra` / `gromore_extra` | string | 客户端透传 JSON。支持 `ad_session_id`、`reward_scene`、`srv_env`、`boost_round_id` |

### `extra` 里的 `boost_round_id`

客户端生成的「这条广告属于哪一轮膨胀」标签(32 位十六进制,同 `ad_session_id` 格式),随发奖记录存进 `ad_reward_record.boost_round_id`。

**它不参与任何发奖判定** —— 发多少、发不发完全不受影响,只是让 [`/ad/reward-result`](./ad-reward-result.md) 能把同一轮的 granted 记录求和成 `round_coin`(客户端「恭喜累计获得奖励」弹窗显示的数)。

轮次边界由客户端定(只有它知道用户点没点「放弃赚钱」):点「去膨胀」新生成一个 → 点「继续看视频膨胀」复用同一个 → 点「放弃赚钱」/ ✕ / 返回 / 到每日上限 / 跨天 则丢弃。不带此字段(老客户端 / GroMore 偶发丢 extra)时存 NULL,`round_coin` 返 `null`。
| `mediation_rit` | string | 代码位 ID（GroMore 带,目前仅入 raw 备查） |
| `prime_rit` | string | 广告位 ID（同上） |
| `adn_name` | string | 实际出广告的 ADN 名（同上,可用于收益分析） |
| `ecpm` | string | 本次广告 eCPM（同上,可用于收益分析） |
| `sign` | string | 签名,见下 |

**验签**:`sign = SHA256("{m-key}:{trans_id}")` 十六进制（只签 `trans_id`，其余参数不参与）。多激励位共用同一回调 URL → 服务端把各位的 m-key 都配上,`verify_callback_sign_any` 逐个试、任一过即接受。算法细节、m-key 配置项(`PANGLE_REWARD_SECRET_TEST/_TEST_DEDICATED/_PROD`)、为什么这样设计 → 见集成文档 [integrations/pangle](../integrations/pangle.md)。

## 出参
响应 `200`,**响应体必须是 `{"is_verify": bool, "reason": int}`**（GroMore 规范）。

| 字段 | 类型 | 说明 |
|---|---|---|
| `is_verify` | bool | `true`=校验通过、发放奖励（发奖成功 或 当日达上限,均算已处理、不重试） |
| `reason` | int | `is_verify=false` 时的错误码,透传客户端 SDK:`1`=参数缺/坏,`2`=user 不存在;成功为 `0` |

## 错误码
- `403` 验签失败（`bad sign`,留给真请求重试）
- `503` 回调未配置（`pangle_callback_configured=false`）

## 说明
**发奖唯一可信入口**:验签 → 取 `user_id`/`extra` → 按 `reward_scene` 分流 → 幂等处理（按 `trans_id` 去重）。客户端不直接发奖,被破解也刷不到钱。

- `reward_scene=reward_video` 或缺省:普通激励视频。金币按 `eCPM / 1000 * eCPM因子 * 当日次数因子 * 10000` 计算;若回调没有 `ecpm`,会按 `extra.ad_session_id` 查客户端 `/ad/ecpm-report` 的上报值;两边都没有 eCPM 时不发币,记录 `status=ecpm_missing`。
- ~~`reward_scene=signin_boost`~~(签到膨胀):**2026-07 已下线**。它按固定 3000 金币发、与广告实际收益脱钩,产品确认非设计内口径。签到弹窗的「看广告膨胀」现与福利页看视频同走 `reward_video`。现在传 `signin_boost` 会落到「未知场景」分支(不发币,`status=unknown_scene`)。
- 未知 `reward_scene`:不发币,记录 `status=unknown_scene`,返回 `is_verify=false/reason=1`。
- 验签过但参数缺/坏或 user 不存在 → 不发（`is_verify=false` + `reason`）;granted / capped / ecpm_missing / 业务不满足已记录 → `is_verify=true` + `reason=0`。
