Unity LevelPlay
- 自己生成一个私钥:至少 8 个字符、不含空格,最好是 32 位以上的随机字符串。把它和 LevelPlay App Key 一起存进 Kasskeep:
npx @kasskeep/cli ads credentials --id app_xxx --levelplay-private-key-file - --levelplay-app-key <App Key> < ./levelplay-private-key.txtLevelPlay 每个平台有一个 App Key。如果你的 iOS 和 Android App 都上报到同一个 Kasskeep App,就把两个 key 用逗号隔开一起填(--levelplay-app-key <iOS key>,<Android key>,最多 10 个)。没有私钥时,回调返回 422 levelplay_not_configured。
- 在 LevelPlay 的 Apps 里打开你的 App 的操作菜单,选择 Set S2S callback:
- Callback URL,方括号里的宏请原样保留:
https://api.kasskeep.com/v1/ads/levelplay/<app_id>/s2s?appUserId=[USER_ID]&rewards=[REWARDS]&eventId=[EVENT_ID]&itemName=[ITEM_NAME]&placementName=[PLACEMENT_NAME]&adNetwork=[AD_NETWORK]&appKey=[APP_KEY]&auctionId=[AUCTION_ID]LevelPlay 会自己追加 timestamp 和 signature,以及带 custom_ 前缀的自定义参数。
- Private key:与第 1 步相同的字符串。
- 保存前先运行 Callback test;只有应答正确,LevelPlay 才会启用回调。测试用的
eventId会像真实奖励一样被记录,所以请在开发用的 App 上测,或者在映射广告位之前测。
- 映射广告位,每个平台一个 id:
npx @kasskeep/cli ads unit-set --id app_xxx --name revive --levelplay ios:<iOS ad unit id> --levelplay android:<Android ad unit id>- 在客户端用 App Key 和用户 id 初始化 LevelPlay,每次展示前设置动态用户 id 和自定义参数
ad_unit、ad_unit_id、data。下面的 provider 就是这么做的。
signature必须等于md5(timestamp + eventId + appUserId + rewards + privateKey)的十六进制值,其中appUserId要先做 URL 解码。否则返回400 invalid_signature。timestamp(YYYYMMDDHHMM,按 UTC 解读;也接受 10 位秒级和 13 位毫秒级时间戳)必须在服务器时钟 ±1 小时以内,否则返回400 timestamp_out_of_window或400 invalid_timestamp。可用--levelplay-timestamp-window <seconds>(60 到 604800)调整窗口。- 设置了
app_key且回调带有appKey时,它必须是已配置的 key 之一,否则返回400 app_key_mismatch。 - 可选的来源 IP 允许列表:
--levelplay-ip-allowlist levelplay放行 LevelPlay 公布的回调地址;也可以列出 IP 和 IPv4 CIDR。其他来源收到403 ip_not_allowed。传空值即关闭。 - 每个通过校验的回调(包括重复的和未映射的)都应答
200,JSON 体里带"ack": "<eventId>:OK",这正是 LevelPlay 要找的内容。
没有收到正确应答的回调,LevelPlay 会重试(24 小时内 20 次,之后每天一次,持续 7 天)。
| LevelPlay 参数 | 事件字段 |
|---|---|
dynamicUserId,没有则用 appUserId |
app_user_id(32 位十六进制的动态 id 会还原为 UUID) |
rewards |
reward_amount |
itemName |
reward_type |
eventId |
network_transaction_id |
custom_data |
custom_data |
timestamp |
occurred_at |
客户端发送 ad_unit=<your name>、ad_unit_id=<LevelPlay ad unit id> 和 data=<customData>;它们以 custom_ad_unit、custom_ad_unit_id 和 custom_data 回传。Kasskeep 先按 custom_ad_unit_id 查找映射,再按 placementName,最后按 custom_ad_unit 里的名字。如果映射到的名字与 custom_ad_unit 不同,回调按未映射处理。
Provider 示例
Section titled “Provider 示例”import { LevelPlay, LevelPlayInitRequest, LevelPlayRewardedAd } from "unity-levelplay-mediation";import { Kasskeep, type RewardedProvider } from "@kasskeep/react-native";
const ads = new Map<string, LevelPlayRewardedAd>();
const levelplayRewarded: RewardedProvider = { network: "levelplay", init: ({ appKey, appUserId }) => new Promise((resolve, reject) => { const request = LevelPlayInitRequest.builder(appKey!).withUserId(appUserId).build(); LevelPlay.init(request, { onInitSuccess: () => resolve(), onInitFailed: (e) => reject(new Error(String(e?.errorMessage ?? e))) }); }), setUserId: (appUserId) => LevelPlay.setDynamicUserId(appUserId.replace(/-/g, "")), showRewarded: ({ networkAdUnitId, serverParams }) => new Promise((resolve) => { const ad = ads.get(networkAdUnitId); if (!ad) return resolve({ status: "failed" }); let rewarded = false; ad.setListener({ onAdRewarded: () => (rewarded = true), onAdClosed: () => setTimeout(() => resolve({ status: rewarded ? "completed" : "skipped" }), 500), onAdDisplayFailed: () => resolve({ status: "failed" }), onAdLoaded: () => {}, onAdLoadFailed: () => {}, onAdDisplayed: () => {}, }); LevelPlay.setMetaData("LevelPlay_Rewarded_Server_Params", Object.entries(serverParams).map(([k, v]) => `${k}=${v}`)); ad.showAd(); }),};
Kasskeep.configure({ apiKey, baseUrl, appUserId, ads: { rewardedProvider: levelplayRewarded, appKey: "<App Key>", adUnits: { revive: "<ad unit id>" }, testAds: __DEV__ },});初始化完成后,为每个 LevelPlayRewardedAd 创建实例并 loadAd(),存进 ads;每次展示后重新加载。onAdRewarded 可能在 onAdClosed 之后才到,所以加了一点延迟。方法名请以你锁定的插件版本为准。