阅读主题
游戏推广来源接入
游戏推广链接可以将玩家带到合作约定的落地页。游戏服务器必须在第一次成功登录兑换时显式传递 sourceCode,平台才能记录该游戏的推广注册来源。 创建链接、点击链接、下载客户端都不等于完成注册归因。
此能力在平台服务端实现,现有 SDK v0.3.0 无需替换或重新打包。接入方需要修改自己的落地页与可信游戏服务器;SDK 不会自动读取 URL、追踪安装或替游戏服务器补来源参数。
从落地页到首次登录
- 有效推广链接经平台校验后,以 HTTP 303 跳转至合作约定的 HTTPS 落地页,并附加
starlight_source查询参数。该参数是公开渠道标识,不是账号、会话或服务端密钥。 - 游戏落地页读取该参数,随本次登录的
appId、code、codeVerifier交给自己的游戏服务器。跨页面登录需要由宿主妥善保留本次来源,不依赖地址栏在跳转后仍包含参数。 - 游戏服务器校验 App ID 允许列表、授权请求字段和来源格式,添加服务端
X-API-Key,调用POST /api/server/auth/exchange。 - 平台验证授权码与 PKCE,成功登录时对“平台账号 × 逻辑游戏”作首次归因决定。返回的登录会话不包含代理联系方式、合作内部标识或佣金快照。
首次登录前完成接入
第一次成功游戏登录未提供来源,会记录为自然来源。之后补传推广码不会改绑。不能先用普通兑换完成登录,再调用第二次兑换补来源;授权码本身也只能消费一次。
请求字段
使用授权码兑换接口原有三个必填字段,增加一个可选字段:
| 字段 | 约束 | 处理方式 |
|---|---|---|
appId | 当前游戏已批准的 App ID | 游戏服务器检查允许列表及游戏归属 |
code | 本次 SDK 生成的一次性授权码 | 20~100 字符,不能复用 |
codeVerifier | 本次登录配对的 PKCE 验证器 | 43~128 字符;沿用原始登录事务 |
sourceCode | 可选字符串,16~100 位字母、数字、_ 或 - | 从本次落地页的 starlight_source 转交;没有有效格式的来源时省略该字段 |
接口仍使用严格请求体校验。sourceCode: ""、null、超长值或额外属性会导致 VALIDATION_ERROR;不要把整个 URL、Cookie 或客户端输入对象原样转发。客户端的来源只是一条待校验线索,平台会检查渠道有效性和游戏归属,不能由客户端指定代理身份或佣金比例。
H5 宿主转交来源
以下代码是你现有 SDK exchange 回调的接入方式;appId 和 sdkOptions 来自宿主既有配置。来源仅保存在当前页面变量中,跨页面持久化策略由宿主按实际登录流程处理。
ts
const candidate = new URL(location.href).searchParams.get('starlight_source');
const sourceCode = candidate && /^[A-Za-z0-9_-]{16,100}$/.test(candidate)
? candidate
: undefined;
const sdk = new StarlightSDK({
...sdkOptions,
appId,
exchange: async ({ appId, code, codeVerifier }) => {
const response = await fetch('/api/game/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
appId, code, codeVerifier,
...(sourceCode ? { sourceCode } : {}),
}),
signal: AbortSignal.timeout(15000),
});
const result = await response.json();
if (!response.ok) throw new Error(result.error?.code ?? 'GAME_LOGIN_FAILED');
return result;
},
});/api/game/login 属于你自己的游戏服务器。最新仓库的 examples/game-server.mjs 已支持校验并转发可选 sourceCode,原有三字段请求仍兼容。已发布下载包保持不变,旧包自带示例或你此前复制的服务端代码可能只允许三个字段,直接多传来源会被严格校验拒绝;这类接入需要按下一节扩展允许字段、校验和平台请求体,不能只修改页面。
可信游戏服务器兑换
以下函数在游戏服务器执行。allowedAppIds 是你配置的应用允许列表,ACCOUNT_ORIGIN、GAME_SERVER_KEY 从服务端安全配置读取。函数接收游戏登录请求的 JSON,验证后转交四个约定字段,不记录请求凭证。
ts
async function exchangeGameLogin(input: unknown, allowedAppIds: Set<string>) {
if (!input || typeof input !== 'object' || Array.isArray(input)) {
throw new Error('INVALID_GAME_LOGIN_INPUT');
}
const body = input as Record<string, unknown>;
const { appId, code, codeVerifier, sourceCode } = body;
if (
Object.keys(body).some(key => !['appId', 'code', 'codeVerifier', 'sourceCode'].includes(key)) ||
typeof appId !== 'string' || !allowedAppIds.has(appId) ||
typeof code !== 'string' || code.length < 20 || code.length > 100 ||
typeof codeVerifier !== 'string' || !/^[A-Za-z0-9._~-]{43,128}$/.test(codeVerifier) ||
(sourceCode !== undefined &&
(typeof sourceCode !== 'string' || !/^[A-Za-z0-9_-]{16,100}$/.test(sourceCode)))
) {
throw new Error('INVALID_GAME_LOGIN_INPUT');
}
const origin = process.env.ACCOUNT_ORIGIN;
const key = process.env.GAME_SERVER_KEY;
if (!origin || !key) throw new Error('GAME_SERVER_NOT_CONFIGURED');
const response = await fetch(`${origin}/api/server/auth/exchange`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': key },
body: JSON.stringify({
appId, code, codeVerifier,
...(sourceCode === undefined ? {} : { sourceCode }),
}),
redirect: 'error',
signal: AbortSignal.timeout(15000),
});
const result = await response.json();
if (!response.ok) throw new Error(result.error?.code ?? 'EXCHANGE_FAILED');
return result;
}沿用既有服务器的来源校验、限流、错误处理和游戏会话映射。不要把 X-API-Key 放进客户端;不要为推广归因改用演示会话入口。网络超时导致兑换结果不确定时,应重新发起完整 SDK 登录,不能重复消费旧授权码。
首次归因与跨端规则
| 场景 | 当前行为 |
|---|---|
| 新玩家首次成功登录,来源有效且属于当前游戏 | 锁定该游戏的渠道归因 |
| 首次成功登录未传来源 | 锁定自然来源,后续推广不改绑 |
| 已有游戏登录、订单、角色或保留的历史登录证据 | 作为老玩家处理,不转给新渠道 |
| 首次登录携带格式合法、但渠道不存在或不属于当前游戏的来源 | 登录凭证仍按正常规则验证;成功后不建立渠道归因,之后也不会补绑 |
| 同一逻辑游戏从 H5 换到 Android、iOS 或 Windows | 共享该游戏的既有归因,不因换端或换 App ID 重新归因 |
| 另一款逻辑游戏 | 单独作首次归因决定,不能沿用上一款游戏的渠道 |
“注册”在此指该游戏完成有效来源接入后的首次成功登录记录,不等于平台账号注册,也不代表下载量、访问量或实名认证人数。平台不提供通过 IP、设备指纹、应用商店下载或安装自动补归因。原生深链能否传回来源,以及安装前后如何保持来源,需要接入方另外设计并在真实设备验证;当前能力没有自动的延迟深链归因。
渠道停用与订单
渠道暂停、合作被冻结或暂停、代理资格失效、游戏不可用等会使推广链接校验失败,不再正常跳转。已经到达落地页的玩家可能仍带旧来源:格式合法的失效来源不应阻止一笔本来合法的登录,但不会建立新的渠道归因。游戏本身被停用时,登录仍会因应用不可用而被拒绝。
既有归因不会因为停用被改绑。每次创建后续订单时平台会重新检查渠道和合作资格;失效期间新订单不会写入该合作归因,恢复后也不追补这些订单。已创建订单保留创建时的归因和合作条款记录,不随之后的计划编辑改写。仅暂停计划的新申请入口不等于冻结已有合作;停止已有推广应使用实际渠道暂停或合作/平台冻结能力。
当前真实支付、退款对账与佣金出款通道尚未配置。演示支付和平台币订单可以用于验证记录链路,不产生可结算佣金;合作比例、订单金额和归因记录都不能直接当成可提现余额。
接入验收
- [ ] 使用未登录过该逻辑游戏的测试账号,经有效推广落地页完成一次服务器兑换,再在有权限的合作记录中核对归因。
- [ ] 新账号无来源首次登录后再携带推广码,不发生补绑;老玩家和同游戏跨端登录也不改绑。
- [ ] 格式错误来源被校验拒绝;格式合法但失效、错游戏来源不会造成非法归因。
- [ ] 暂停渠道或冻结合作后验证链接失效,以及合法登录不受单纯来源失效影响。
- [ ] 验证失效期间新增订单不追记归因、恢复不补记旧订单;不将演示订单作为可结算佣金。
- [ ] 原生端分别验收直接深链与安装后首次打开,未实现的来源传递不标记为已支持。
实现依据
packages/server/src/app.ts 的 exchangeSchema 与可信兑换流程;packages/server/src/agency-attribution.ts 的首次登录决定、历史登录保护和订单归因校验;packages/server/src/agency.ts 的渠道资格校验。该指南描述平台当前行为,接入方服务器与真实设备仍需逐项验收。