Skip to content

Unity LevelPlay

  1. Generate a private key yourself: at least 8 characters without spaces, ideally a random string of 32 or more. Store it in Kasskeep together with the LevelPlay App Key:
Terminal window
npx @kasskeep/cli ads credentials --id app_xxx --levelplay-private-key-file - --levelplay-app-key <App Key> < ./levelplay-private-key.txt

LevelPlay has one App Key per platform. If both your iOS and Android apps report to the same Kasskeep app, give both keys separated by a comma (--levelplay-app-key <iOS key>,<Android key>, at most 10). Without a private key callbacks return 422 levelplay_not_configured.

  1. In LevelPlay, Apps, open the actions of your app and choose Set S2S callback:
    • Callback URL, with the bracketed macros exactly as written:
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 appends timestamp and signature itself, and custom parameters with a custom_ prefix.

  • Private key: the same string as in step 1.
  • Run Callback test before saving; LevelPlay activates the callback only after a correct answer. The test eventId is recorded like a real reward, so test against a development app or before mapping ad units.
  1. Map the ad unit, one id per platform:
Terminal window
npx @kasskeep/cli ads unit-set --id app_xxx --name revive --levelplay ios:<iOS ad unit id> --levelplay android:<Android ad unit id>
  1. On the client, initialize LevelPlay with the App Key and the user id, and before each show set the dynamic user id and the custom parameters ad_unit, ad_unit_id and data. The provider below does this.
  • signature must equal hex md5(timestamp + eventId + appUserId + rewards + privateKey), with appUserId URL-decoded. Otherwise 400 invalid_signature.
  • timestamp (YYYYMMDDHHMM, read as UTC; 10-digit seconds and 13-digit milliseconds are also accepted) must be within ±1 hour of the server clock: 400 timestamp_out_of_window or 400 invalid_timestamp. Change the window with --levelplay-timestamp-window <seconds> (60 to 604800).
  • When app_key is set and the callback carries appKey, it must be one of the configured keys: 400 app_key_mismatch.
  • Optional source IP allowlist: --levelplay-ip-allowlist levelplay allows LevelPlay’s published callback addresses; you can also list IPs and IPv4 CIDRs. Others get 403 ip_not_allowed. An empty value turns it off.
  • Every verified callback, including duplicates and unmapped ones, answers 200 with "ack": "<eventId>:OK" in the JSON body, which is what LevelPlay looks for.

LevelPlay retries callbacks that get no correct answer (20 times within 24 hours, then daily for 7 days).

LevelPlay parameter Event field
dynamicUserId, else appUserId app_user_id (a 32-digit hex dynamic id is restored to a UUID)
rewards reward_amount
itemName reward_type
eventId network_transaction_id
custom_data custom_data
timestamp occurred_at

The client sends ad_unit=<your name>, ad_unit_id=<LevelPlay ad unit id> and data=<customData>; they return as custom_ad_unit, custom_ad_unit_id and custom_data. Kasskeep looks up the mapping by custom_ad_unit_id, then by placementName, then by the name in custom_ad_unit. If the mapped name differs from custom_ad_unit, the callback is treated as unmapped.

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__ },
});

Create and loadAd() each LevelPlayRewardedAd after init and keep it in ads; reload after every show. onAdRewarded can arrive after onAdClosed, hence the short delay. Check method names against the plug-in version you pin.