Skip to main content
Integration for HTML5 Sites/Apps Updated May 13

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.

CallbackWhere it runsWhat it's for
Local callback (adStatusCallbackFn)In the browser, inside your pageOptimistic UI: dismiss the player, advance the game, queue a reward animation
Web callbackServer-to-server, AppLixir → your backendSource 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" | "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
  • 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. When an ad completes, AppLixir sends an HTTP GET to a URL you provide, with the parameters described below.

Configuration

  1. In client.applixir.com, open the Server-side reward webhook card.
  2. Set the URL to an HTTPS endpoint on your backend that returns 200 OK for the happy path.
  3. Set the Secret to a long random string. Store the same secret on your backend — depending on the mode, you use it to verify the request signature.
  4. Choose the Mode (see below).

Modes

The Mode you pick on the dashboard controls what AppLixir adds to the request:

ModeWhat AppLixir addsUse it for
No MD5 or TIDNothing extra — verify via the secretKey paramQuick tests, low-stakes rewards
MD5 OnlyAn MD5 signatureProduction — verify the signature before crediting
MD5 and TIDAn MD5 signature and a unique tid (transaction ID)Production with idempotency — verify + dedupe on tid

MD5 and TID is recommended for production. Verify the signature rather than trusting the plaintext secretKey (which is deprecated for MD5 modes and will be removed).

Request format

Every request includes:

ParamAlways sentNotes
gameApiKeyyesThe game's API key
gameIdyesThe game's ID
userIdif you passed itThe userId from your player options (see below)
customDataif you passed itURL-encoded JSON. Not part of the signature
secretKeyyes (all modes)The raw secret, plaintext. On MD5 modes this is deprecated — verify signature instead; it will be removed for MD5 modes in a future version
signatureMD5 Only and MD5 and TIDLowercase hex MD5 (see formula)
tidMD5 and TID onlyUnique per callback — dedupe on this

Verifying the signature (MD5 modes)

Recompute the MD5 over the concatenation below using your stored secret, then compare to the signature param. If userId was not provided it is the empty string; in MD5 Only mode there is no tid segment.

signature = md5( gameApiKey + gameId + userId + tid + secret ) // MD5 and TID
signature = md5( gameApiKey + gameId + userId + secret ) // MD5 Only

PHP:

$expected = md5($_GET['gameApiKey'] . $_GET['gameId'] . ($_GET['userId'] ?? '') . ($_GET['tid'] ?? '') . $SECRET);
if (!hash_equals($expected, $_GET['signature'] ?? '')) { http_response_code(403); exit; }

Node:

import crypto from "crypto";
const q = req.query;
const expected = crypto.createHash("md5")
.update(`${q.gameApiKey}${q.gameId}${q.userId ?? ""}${q.tid ?? ""}${SECRET}`)
.digest("hex");
if (expected !== q.signature) return res.sendStatus(403);

Reward grant rule

Your backend is the system of record for rewards. The flow:

  1. Player watches the ad → AppLixir player fires status.type === "complete" → your client shows "Reward incoming…"
  2. AppLixir sends the GET to your webhook
  3. Your backend verifies the signature (MD5 modes), dedupes on tid (MD5 and TID), credits the player
  4. 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. Network retries 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.

Reconciliation

If the optimistic UI fired complete but no webhook arrives within a few seconds (consent block, geo issue, network drop after complete), 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 player options so the webhook can attribute the reward to the right player:

initializeAndOpenPlayer({
apiKey: "YOUR-API-KEY",
userId: "player-7890",
customData: { level: 12, sessionId: "abc-123" },
// ... other options
});

userId surfaces as the userId param (and is part of the signature). customData surfaces as a URL-encoded JSON customData param.

Testing the callback

You can test the integration end-to-end with webhook.site:

  1. Open webhook.site. It auto-generates a unique URL for you.
  2. In the AppLixir dashboard, paste that URL in, set any temporary value as the secret, and choose No MD5 or TID (so signature verification isn't needed for the test).
  3. 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 an HTTPS endpoint reachable from the public internet
  • Mode is MD5 Only or MD5 and TID (not No MD5 or TID)
  • Backend verifies the signature on every incoming request
  • Backend dedupes on tid (MD5 and TID mode)
  • Backend credits the player only inside the web-callback handler (not on the local complete event)
  • Client UI uses the local complete for optimistic feedback only, and refreshes from server state to confirm the reward
  • Reconciliation path exists for the "optimistic UI fired, no webhook arrived" case