Step 4: Setting up callbacks
AppLixir delivers ad lifecycle information through two callbacks. They serve different purposes and you need both for a production integration.
| Callback | Where it runs | What it's for |
|---|---|---|
Local callback (adStatusCallbackFn) | In the browser, 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: Use the local callback to drive what the player sees. Use the web callback to drive what the player gets.
Local callback
You wire the local callback into the adStatusCallbackFn option when you initialize the player (see Step 3). It receives a status object:
{ type: "allAdsCompleted" | "click" | "complete" | "firstQuartile" | "loaded" | "midpoint" | "paused" | "started" | "thirdQuartile" | "skipped" | "manuallyEnded" | "thankYouModalClosed" | "consentDeclined" }
Event types
- allAdsCompleted — ads manager is done playing all valid ads in the response, or the response returned no valid ads
- click — the ad was clicked
- complete — the ad finished playing (the one event the player actually watched it through)
- firstQuartile — playhead crossed the first quartile
- loaded — ad data is available
- midpoint — playhead crossed the midpoint
- paused — the ad is paused
- started — the ad started playing
- thirdQuartile — playhead crossed the third quartile
- skipped — the user skipped the ad
- manuallyEnded — the user manually ended the ad
- thankYouModalClosed — the user closed the thank-you modal
- consentDeclined — the user declined consent for personalized ads
Using complete for optimistic UI
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 fully untrusted: DevTools can fire arbitrary callbacks, extensions can replay events, multi-tab sessions can double-fire complete for the same impression. Treat the local complete as optimistic UX only; let the web callback (below) be the system of record.
Errors
When the player surfaces an error, you can pull the details via getError() — it returns a type (one of ~38 VAST/IMA error strings), an errorCode, an errorMessage, and an innerError. The full enumerated list lives in the Local callback error codes reference.
Web callback
This is the server-to-server channel and is the only channel that should grant persistent rewards. AppLixir POSTs an HMAC-signed payload to a URL you provide, on every ad completion, after server-side validation.
Configuration
- In
client.applixir.com, open the Server-side reward webhook card. - Set the URL to an endpoint on your backend that accepts a POST and runs
200 OKfor the happy path. - Set the Secret to a long random string. Store the same secret on your backend — you use it to verify the HMAC signature on every incoming request.
- Choose the Mode that matches your environment.
Reward grant rule
Your backend is the system of record for rewards. The flow:
- Player watches the ad → AppLixir player fires
status.type === "complete"→ your client shows "Reward incoming…" - AppLixir validates the impression server-side → POSTs the signed payload to your webhook
- Your backend verifies the HMAC, dedupes the impression ID, credits the player
- Your client refreshes from server state and shows the granted reward
Idempotency
Treat the webhook as at-least-once delivery. Network retries, replays, and edge restarts can deliver the same impression more than once. Dedupe on the webhook's impression ID before crediting — a second arrival for an ID you've already processed should be a no-op 200 OK, not a duplicate grant.
Reconciliation
If the optimistic UI fired complete but no webhook arrives within a few seconds (consent block, geo issue, network drop after complete, AppLixir-side validation rejection), do not roll back the UI from the client. Refresh from server state instead — let the server be authoritative and the UI will reconcile itself.
Passing context to the webhook
Pass userId and customData in your options so the webhook can attribute the reward to the right player:
window.invokeApplixirVideoUnit({
appKey: "YOUR-API-KEY",
userId: "player-7890",
customData: { level: 12, sessionId: "abc-123" },
// ... other options
});
Both fields surface on the web callback payload.
Testing the callback
You can test the integration end-to-end with webhook.site:
- Open webhook.site. It auto-generates a unique URL for you.
- In the AppLixir dashboard, paste that URL into Callback Settings and set any temporary value as the secret (e.g.
"123456"— backend signature verification is not required for the webhook.site test). - Run a rewarded ad in your game and wait for it to complete. The request should appear in webhook.site.
It may take a few attempts before the callback shows up — there's a short propagation delay after updating callback settings. Once active, requests come through consistently.
Production checklist
- Web callback URL is set and reachable from the public internet
- Backend verifies the HMAC signature on every incoming request
- Backend dedupes on the impression ID
- Backend credits the player only inside the web-callback handler (not on the local
completeevent) - Client UI uses the local
completefor optimistic feedback only, and refreshes from server state to confirm the reward - Reconciliation path exists for the "optimistic UI fired, no webhook arrived" case