Publisher integration

Two small JavaScript files are the whole integration: the verification tag (measures delivery and viewability, reports both keys) and the earn mark (renders the “this ad pays” badge on registered devices and handles the claim tap). Both are dependency-free and fail safe — if anything is misconfigured, your ads run exactly as before.

1. Get your publisher ID

Publisher IDs are issued by the My Ad Rewards team during onboarding; there is no self-serve registration endpoint yet. Your ID (for example pub_123) keys every event you send. If you also fund rewards for your own audience, your portal account owns your publisher-funded programs, and the business portal shows your traffic and earnings. Contact us to start onboarding.

2. Add the verification tag

Host adrewards.js (current release v0.6.10, under 5 KB gzipped) with your own static assets and include it on every page that carries rewarded placements:

<script async src="https://your-cdn.example/adrewards.js"
  data-adrewards
  data-publisher-id="pub_123"
  data-app-id="app_9"
  data-endpoint="https://api.myadrewards.com/v1/events"></script>

<div data-ar-view="homepage-hero" data-creative-id="adv_42:cre_7"></div>

Set the endpoint explicitly. The current tag build's built-in default points at a legacy host. Production integrations must pass data-endpoint="https://api.myadrewards.com/v1/events" (or the endpoint configure option) as shown above.

Script attributeRequiredMeaning
data-adrewardsyesMarks the script for auto-configuration on load.
data-publisher-idyesYour publisher ID. Without it the tag stays inert.
data-app-idnoYour app/site identifier, sent as app_id for attribution and earnings breakdowns.
data-endpointproduction: yesEvents endpoint URL (see the note above).
data-campaign-idnoDefault campaign attribution for every slot on the page.
data-creative-idnoDefault creative for slots that don't set their own.
data-consent-stringnoAn IAB TCF v2 / GPP consent string, forwarded as consent_string on every event.
data-visibility-gatenoWhen present, device confirms are suppressed while the tab is hidden.
data-unload-beaconnoWhen present, queued events flush via navigator.sendBeacon on page hide.

With the script in place, any element carrying data-ar-view="<slot name>" is tracked automatically; its data-creative-id identifies the creative. That declarative route is enough for most pages.

3. Programmatic use

For dynamic pages and players, drive the tag directly:

adrewards.configure({
  publisherId: "pub_123",
  appId: "app_9",
  endpoint: "https://api.myadrewards.com/v1/events"
});

var handle = adrewards.track(document.querySelector("#hero"), {
  creativeId: "adv_42:cre_7", slot: "hero"
});
// handle.nonce — this view's nonce; handle.stop() detaches early

var video = adrewards.trackVideo(document.querySelector("#preroll"), {
  creativeId: "adv_42:cre_7", slot: "preroll", completionPct: 75
});
configure() optionDefaultMeaning
publisherId— (required)Your publisher ID.
endpointlegacy hostEvents endpoint. Set https://api.myadrewards.com/v1/events in production.
appId""Sent as app_id.
creativeId, slot""Page-level defaults; per-call options override them.
campaignId""Sent as campaign_id on both halves of the pair.
consentString""TCF v2 / GPP string forwarded as consent_string.
viewability{ ratio: 0.5, durationMs: 1000 }Viewability threshold: fraction of pixels visible, continuously, before the device confirms.
visibilityGatefalseSuppress device confirms while the tab is hidden; in-progress view time is discarded on hide.
unloadBeaconfalseFlush the queue with sendBeacon on pagehide.
onReceiptnullCallback fired once per view with the settled/expired receipt (see Receipts below).
receiptPoll{ maxAttempts: 10, intervalMs: 5000 }Receipt polling budget; optional timeoutMs bounds it by wall clock instead.

track(el, options) accepts per-slot creativeId, slot, campaignId, and consentString overrides and returns { stop(), nonce }, or null when consent is off or the element is already tracked. trackVideo(el, options) is the video form: it reports delivery immediately and fires one completion_confirm when playback reaches completionPct (default 100), carrying the farthest point watched as proof.

Consent comes first

The tag's rewards consent is off by default and persists per publisher in first-party storage. Nothing is queued or sent until you opt the device in — wire it to your own consent flow:

adrewards.isEnabled();  // false on first load
adrewards.enable();     // opt in — resumes reporting, drains any persisted queue
adrewards.disable();    // opt out — track() becomes a silent no-op

configure() never changes the stored consent choice. Viewability is measured with IntersectionObserver; events go out fetch-first with exponential-backoff retry and a sendBeacon fallback, through a durable queue that survives page reloads.

4. What the tag sends

Two events per verified view, both to POST /v1/events:

{
  "v": 1, "event_type": "delivery_report", "source": "publisher",
  "publisher_id": "pub_123", "app_id": "app_9",
  "identity_token": "…32 hex chars — first-party, rotates every 24h, scoped to this publisher…",
  "creative_id": "adv_42:cre_7", "slot": "homepage-hero",
  "nonce": "…16 hex chars, shared by both halves…",
  "ts": "2026-09-27T20:00:00.000Z", "tag": "0.6.10"
}

{
  "v": 1, "event_type": "viewability_confirm", "source": "device",
  "publisher_id": "pub_123", "app_id": "app_9",
  "identity_token": "…same token…", "nonce": "…same nonce…",
  "ts": "2026-09-27T20:00:01.000Z",
  "quartile_ms": [250, 500, 750, 1000], "view_duration_ms": 1000
}
FieldRequiredMeaning
vyesEvent schema version — always 1.
event_typeyesdelivery_report, viewability_confirm, or completion_confirm.
sourceyesWhich key this report is: publisher or device.
publisher_idyesYour publisher ID.
identity_tokenyesThe device identity for this publisher (see the earn mark's registration gate).
nonceyesPer-view value shared by both halves; the server deduplicates on (source, nonce).
tsyesRFC 3339 timestamp. The API rejects events older than 24 hours or more than 5 minutes in the future.
app_id, campaign_id, creative_id, slotnoAttribution fields. creative_id and campaign_id are how advertiser funding bindings match.

The events endpoint answers 202 with the outcome — for example { "accepted": true, "duplicate": false, "settled": true, "claimable": true, "claim_expires_at": "…" } once a pair completes — or 422 when a report fails validation. The device half also carries viewability proof (quartile_ms, view_duration_ms; for video, completion_pct and view_duration_ms).

Receipts: following one view

Every nonce has a receipt at GET /v1/events/receipts/{nonce} — no session needed; the unguessable nonce is the capability. The tag polls it for you after the device confirm sends: pass onReceipt to configure() and it fires exactly once with the receipt (status: "settled" or "expired"), or use await adrewards.getReceipt(nonce) for a single lookup. Receipt fields: id, status, points_issued, points_claimable + claim_expires_at while a reward waits to be claimed, mode, settled_at, created_at.

5. Through Google Tag Manager

Import gtm/template.tpl from the tag package as a custom template, create a tag from it, and set your Publisher ID (required), App ID, and optionally Campaign ID, Consent String, the page-visibility gate, the unload beacon, and the events endpoint — set it to https://api.myadrewards.com/v1/events. For consent, fire a Custom HTML tag that calls adrewards.enable() once your consent manager records the visitor's opt-in.

6. Through your ad server

If your ads are trafficked through an ad server (for example Google Ad Manager), the tag can ride with the creative instead of living on the page — the same position any verification tag occupies. Traffic adrewards.js as a third-party tag on the creative, passing that creative's creative_id (and campaign_id) through the snippet attributes or configure(), and point the tag's track()/trackVideo() at the rendered ad container or player. Reports go directly from the viewer's device to the My Ad Rewards API; your ad server only carries the script. Creative IDs must match the advertiser's funding bindings exactly — see the advertiser guide.

7. Add the earn mark

The earn mark is the badge that tells a viewer an ad pays — and, when the view verifies, becomes the claim button. Host earnmark.js (v2.0.0, ~4.4 KB gzipped) with your assets and load it after the tag:

<script src="https://your-cdn.example/earnmark.js"
        data-publisher-id="pub_123"
        data-api-base="https://api.myadrewards.com/v1"></script>

<div data-adrewards-ad>
  <!-- your ad creative -->
</div>
Script attributeRequiredDefaultMeaning
data-publisher-idyes—Your publisher ID. The snippet renders nothing without it.
data-api-baseproduction: yeslegacy hostAPI base for the registration gate, receipt polling, and claims. Set https://api.myadrewards.com/v1.
data-earn-textnoThis ad paysBadge text in the pays state (set as text, never parsed as HTML).
data-themenolightlight (white pill) or dark (dark pill). Claim states keep their own colors.
data-selectorno[data-adrewards-ad]CSS selector for the ad containers to badge.

Mark each rewarded container with data-adrewards-ad. To follow a specific impression, set data-adrewards-nonce on the container (from your own track() handle); without one, the snippet tracks the container itself through the page's tag. The same options can be set before the script loads via window.AdRewardsEarnMarkConfig, or per call via AdRewardsEarnMark.render({ … }). For dynamically inserted ads, call await AdRewardsEarnMark.render() after insertion — it returns the number of containers badged and is idempotent.

The registration gate

Before rendering anything, the snippet reads the identity token your page's tag carries for this publisher and asks the API (GET /v1/identity/device-state) whether that device is registered to a viewer account. Registered means the token was server-minted for a real account user + device and the device holds an active enrollment — a tag's self-generated token never counts. On an unregistered device the snippet renders nothing at all — no badge, no prompt, no overlay — and any check failure fails closed the same way.

StateLookMeaning
pays“This ad pays · Earn N pts”, non-interactiveRegistered device; the impression is not (yet) claimable.
claimGreen “Tap to claim · N pts” button, keyboard-operableThe pair verified; points are held for this viewer.
retry“Claim failed — tap to retry”The claim request failed; still tappable.
claimed“Claimed · +N pts”, non-interactivePoints landed in the wallet — here, or via the wallet/notification; the badge follows the receipt.

The tap posts the claim with the tag's identity token (POST /v1/events/receipts/{nonce}/claim); the snippet polls the receipt (default every 3 s, up to 40 attempts) to follow the state. The badge sits at the container's top-right and never blocks ad clicks except in the tappable claim state. Privacy: the snippet talks only to the My Ad Rewards API, sets no cookies, and does no fingerprinting.

8. Testing and go-live

There is no separate sandbox host. Test on the production API with a dedicated test publisher ID and a test funding program — IDs, programs, and balances are fully separate — then switch the snippet to your production ID to go live. The earn mark package also ships a demo page that stubs the registration gate so you can see every badge state without a backend.

Go-live checklist:

  1. 1

    Consent wired: adrewards.isEnabled() returns true after your opt-in flow runs.

  2. 2

    adrewards.status() shows the queue draining (queueDepth returning to 0, queueFails at 0); adrewards.flush() and adrewards.retry() are there if you need them.

  3. 3

    A test view's receipt moves pending → matched → claimable (watch it via onReceipt or getReceipt(nonce)).

  4. 4

    On a registered device the badge appears and the claim tap lands the points; on an unregistered device nothing renders.

  5. 5

    Your publisher report in the business portal shows the day's events and settled pairs.