Step 5: Callbacks & rewards
AppLixir delivers ad lifecycle information through two channels. They serve different purposes and a production integration uses both.
| Channel | Where it runs | What it's for |
|---|---|---|
Local callback (adStatusCallbackFn) | In the browser/WebView, inside your page | Optimistic UI: dismiss the player, advance the game, queue a reward animation |
| Web callback | Server-to-server, AppLixir → your backend | Source of truth for granting persistent rewards (currency, items, XP) |
Rule of thumb: the local callback drives what the player sees; the web callback drives what the player gets.
Local callback
You wire it into adStatusCallbackFn when you open the player. It receives a
status object whose type is one of:
{ type: "allAdsCompleted" | "click" | "complete" | "firstQuartile" | "loaded"
| "midpoint" | "paused" | "started" | "thirdQuartile" | "skipped"
| "manuallyEnded" | "consentDeclined" }
Event types
- loaded — ad data is available
- started — the ad started playing
- firstQuartile / midpoint / thirdQuartile — playhead crossed 25% / 50% / 75%
- complete — the ad finished playing (the user watched it through)
- allAdsCompleted — the ads manager is done, or the response returned no valid ads
- click — the ad was clicked
- paused — the ad is paused
- skipped — the user skipped the ad
- manuallyEnded — the user manually ended the ad
- consentDeclined — the user declined consent for personalized ads
complete is optimistic, not authoritative
status.type === "complete" is the right hook to dismiss the player and show
"reward incoming…" — but it is not the right hook to grant a persistent
reward. The browser is untrusted: DevTools can fire callbacks, extensions can
replay events, multi-tab sessions can double-fire complete for one impression.
Treat the local complete as optimistic UX only.
In the useRewardedAd hook,
this is exactly why showAd() resolving true means "show the optimistic
reward," and the real credit comes from your server.
React Native bridge
In a WebView there is no shared JS context — the hosted page forwards events to
native via window.ReactNativeWebView.postMessage and you handle them in
onMessage (see Step 4).
The same rule applies: the reward message is optimistic; the server grants.
Web callback (server-to-server)
This is the only channel that should grant persistent rewards. When an ad
completes, AppLixir sends an HTTP GET to a URL you provide, signed
according to the Mode you choose.
Configuration
- In client.applixir.com, open the Server-side reward webhook card.
- Set the URL to an HTTPS endpoint on your backend that returns
200 OKon success. - Set the Secret to a long random string. Store the same secret on your backend to verify the request signature.
- Choose the Mode — use MD5 and TID for production.
The exact request params, the MD5 signature formula, and PHP/Node verify snippets are documented once in HTML5 Step 4 → Web callback — the server-to-server contract is identical for React.
The flow
- Player watches the ad → local
complete(or the RNrewardmessage) → your client shows "reward incoming…" - AppLixir sends the
GETto your webhook - Your backend verifies the
signature, dedupes ontid(MD5 and TID mode), credits the player - Your client refreshes from server state and shows the granted reward
Idempotency
In MD5 and TID mode, treat the webhook as at-least-once delivery.
Retries, replays, and edge restarts can deliver the same tid more than once.
Dedupe on tid before crediting — a second arrival for a tid you've already
processed should be a no-op 200 OK, not a duplicate grant.
Next: Updating ads.txt.