---
title: 反欺诈 — 广告反作弊
---
# 广告反作弊
这是一份**场景指南**。核心接入不变——你照常加载 [Web SDK](../web-sdk)、读取
[裁决](../verdict-reference),并从[数据 API](../data-api) 拉取数据,和那几页里描述的
完全一样。本页只补充广告反作弊场景特有的那一点内容。
广告反作弊把机器人与无效流量挡在付费投放之外,让广告主与广告平台为真实的点击和展示
付费——而非虚假量。它与其它使用场景的不同之处在于:**访客由第三方送来**,且双方需要
就一份独立的、逐点击的真人/机器人结论达成一致。
## 源头实时拦截
反欺诈在入口处 inline 运行、实时给出裁决——它不是事后报表。访客落在谁的页面上,谁就能立刻据裁决处置:
- **广告主**在落地页跑 SDK:无效流量一到达就被识别——并可选触发验证码——在它进入转化漏斗、污染报表之前就拦住。
- **流量平台**在自己的 pre-lander 或跳转页跑 SDK:点击在**被转发之前**就先验一道,机器人在入口就被拦下,根本不会变成已投递流量。
唯一前提是要有一张能跑 SDK 的页:裁决需要浏览器信号,纯服务端 302 跳转没有采集信号的地方——加一个极轻的中间页,同样能实时拦截。
签名 click token 与数据 API 在此之上做两端对账;实时 inline 拦截是主要机制,事后 API 清洗是没有中间页时的兜底。
## 两端角色
- **广告主(Advertiser)** —— 拥有访客落地的页面。在该页面上运行 Web SDK,为每次到达的
访问拿一份裁决,并从数据 API 拉取裁决,以核对自己被计费的部分。
- **服务商(Provider,流量/广告来源)** —— 投递点击。它需要**证明自己投递的质量**,
因此为每次点击签发一个点击 token,之后用独立裁决去比对自己的投递报表。
双方都用各自的应用凭据向[数据 API](../data-api) 鉴权,并读取同一份逐点击裁决——正是
这一点让他们无需互信对方的原始日志即可对账。
## 端到端流程
下面的时序图展示了一次点击如何从服务商的投递,经访客与广告主落地页,最终落到同一份
裁决上——而**两个账号之后各自从自己一侧读到这同一行**。
```mermaid
sequenceDiagram
autonumber
participant P as 流量方(Provider / 账号 B)
participant V as 访客(Visitor)
participant A as 广告主落地页 + SDK
participant F as 反欺诈后端(Fraud Prevention API)
P->>P: 用自己的 bot_kid + secret 签 click_token(内含 pkid)
P->>V: 把 token 拼进投放链接 ?_ctk=click_token
V->>A: 点击 → 落到广告主落地页(URL 携带 _ctk)
A->>A: SDK 从 URL 读取 _ctk
A->>F: 加密 POST /bot/verify
(app_key = 广告主,click_token = 服务商所签)
F->>F: app_key → advertiser_app_id,
验签 pkid → provider_app_id,出裁决,
一行写入两端 app_id
F-->>A: verdict → onVerdict
A->>A: 按 verdict.action 处理(记录 / 拦截 / 弹验证码)
Note over P,F: 对账(跨账号)
A->>F: 广告主用自己的 App-Key 查 GET /v1/bot
F-->>A: 读到同一行(匹配 advertiser_app_id)
P->>F: 流量方用自己的 App-Key 查 GET /v1/bot
F-->>P: 读到同一行(匹配 provider_app_id)
```
关键特性在于:**两个 app_id 落在同一行上。** 广告主按 `advertiser_app_id` 匹配读到它,
流量方按 `provider_app_id` 匹配读到它——两个不同账号,各用各自的 App-Key 查询,各自
落到同一份独立裁决上。双方无需暴露或互信对方的原始日志,即可就某次点击发生了什么
达成一致。
## 点击 token
**点击 token(click token)**把服务商投递的某次点击,与该次访问最终收到的裁决关联
起来。它是双方据以结算的逐点击标识。反欺诈的其它使用场景无需点击 token——不用它也能
对账裁决。
流程:
1. **签发** —— 服务商在把访客路由到广告主落地页时,获取一个已签名的点击 token
(每次点击一个)。
2. **拼接到目标 URL** —— 把签发的 token 作为查询参数拼到落地 URL 上:
```
https://advertiser.example/lp?click_token=ct_xxxxxxxx
```
3. **在页面读取** —— [Web SDK](../web-sdk) 会自动从 URL 读取该 token。若你使用不同的
参数名,设置 `tokenParam`:
```js
BotSignal.init({ appKey: 'YOUR_APP_KEY', tokenParam: 'click_token' });
```
4. **对账** —— 之后通过数据 API 查回该点击,并与服务商的投递报表关联。
::: info
该 token 在签发给你时就已签名——你只需把它**透传到落地 URL** 即可。你这边无需做任何
签名或计算。
:::
### `tokenParam` 选项
在这个场景下,Web SDK 多出一个选项:
| 选项 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `tokenParam` | string | 自动识别 | URL 查询参数名,用于携带服务商的点击 token(如 `click_token`)。SDK 会从页面 URL 中读取它,并把裁决与该次点击关联。若不使用点击 token,保持默认即可。 |
## 对账与结算
一旦访问携带了点击 token,双方就基于同一份独立裁决结算:
- **获取单次点击的裁决** —— 例如用于申诉或确认某次点击:
```bash
GET /v1/bot/verdict?click_token=ct_xxx
X-App-Key: YOUR_APP_KEY
X-App-Secret: YOUR_APP_SECRET
```
- **导出逐点击行** —— 拉取一个时间范围,把每行的 `click_token` 与你自己的点击日志关联:
```bash
GET /v1/bot/export?from=2026-06-01&to=2026-06-30&format=csv
X-App-Key: YOUR_APP_KEY
X-App-Secret: YOUR_APP_SECRET
```
每一行携带该次访问的 `click_token`(若存在)、时间戳,以及裁决字段
(`is_bot`、`score`、`level`、`action`)。
广告主把被标记和机器人点击从付费中剔除;服务商用同一份结论比对自己的投递报表。因为
双方读取的是**同一份独立裁决**,结算不依赖任何一方的原始日志。
## 安全模型
`cid` 与 click token 走在 URL 里,所以把它们当**标识符**、而不是**凭证**:
- **读裁决必须 App-Key + App-Secret。** 数据 API 每次调用都用你的 app secret 鉴权(常量时间比较),且只返回"你是其中广告主或流量方"的行。光有 `cid` 查不到任何东西——路人从 URL 抄走它,既没有 secret、也不是这行的任何一端。
- **流量方由签名绑定。** click token 用流量方 secret 做 HMAC 签名、带流量方 `pkid`,无法伪造;同一 `cid` 只能被认领一次(防重放)。
- **可选:把广告主也绑上。** 把目标广告主的 `app_key` 作为 `aud` 签进 token。验证时要求页面的 app_key 与 `aud` 一致——这样发给广告主 A 的 token 就无法在广告主 B 身上归属裁决。不签 `aud` 则任意广告主页面都可承接。
最终效果:某个 `cid` 的裁决,只有钉在它上面的两个 app 能读——跑了 SDK 的那个广告主、出示了 token 的那个流量方,各自用自己的 App-Secret 鉴权。
## 下一步
- [Web SDK](../web-sdk) —— 在落地页收集裁决
- [裁决字段参考](../verdict-reference) —— 每个字段及处理方式
- [数据 API](../data-api) —— 服务端拉取与对账裁决