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 attribute | Required | Meaning |
|---|---|---|
data-adrewards | yes | Marks the script for auto-configuration on load. |
data-publisher-id | yes | Your publisher ID. Without it the tag stays inert. |
data-app-id | no | Your app/site identifier, sent as app_id for attribution and earnings breakdowns. |
data-endpoint | production: yes | Events endpoint URL (see the note above). |
data-campaign-id | no | Default campaign attribution for every slot on the page. |
data-creative-id | no | Default creative for slots that don't set their own. |
data-consent-string | no | An IAB TCF v2 / GPP consent string, forwarded as consent_string on every event. |
data-visibility-gate | no | When present, device confirms are suppressed while the tab is hidden. |
data-unload-beacon | no | When 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() option | Default | Meaning |
|---|---|---|
publisherId | — (required) | Your publisher ID. |
endpoint | legacy host | Events 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. |
visibilityGate | false | Suppress device confirms while the tab is hidden; in-progress view time is discarded on hide. |
unloadBeacon | false | Flush the queue with sendBeacon on pagehide. |
onReceipt | null | Callback 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
}
| Field | Required | Meaning |
|---|---|---|
v | yes | Event schema version — always 1. |
event_type | yes | delivery_report, viewability_confirm, or completion_confirm. |
source | yes | Which key this report is: publisher or device. |
publisher_id | yes | Your publisher ID. |
identity_token | yes | The device identity for this publisher (see the earn mark's registration gate). |
nonce | yes | Per-view value shared by both halves; the server deduplicates on (source, nonce). |
ts | yes | RFC 3339 timestamp. The API rejects events older than 24 hours or more than 5 minutes in the future. |
app_id, campaign_id, creative_id, slot | no | Attribution 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 attribute | Required | Default | Meaning |
|---|---|---|---|
data-publisher-id | yes | — | Your publisher ID. The snippet renders nothing without it. |
data-api-base | production: yes | legacy host | API base for the registration gate, receipt polling, and claims. Set https://api.myadrewards.com/v1. |
data-earn-text | no | This ad pays | Badge text in the pays state (set as text, never parsed as HTML). |
data-theme | no | light | light (white pill) or dark (dark pill). Claim states keep their own colors. |
data-selector | no | [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.
| State | Look | Meaning |
|---|---|---|
pays | “This ad pays · Earn N pts”, non-interactive | Registered device; the impression is not (yet) claimable. |
claim | Green “Tap to claim · N pts” button, keyboard-operable | The 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-interactive | Points 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
Consent wired:
adrewards.isEnabled()returnstrueafter your opt-in flow runs. - 2
adrewards.status()shows the queue draining (queueDepthreturning to 0,queueFailsat 0);adrewards.flush()andadrewards.retry()are there if you need them. - 3
A test view's receipt moves
pending → matched → claimable(watch it viaonReceiptorgetReceipt(nonce)). - 4
On a registered device the badge appears and the claim tap lands the points; on an unregistered device nothing renders.
- 5
Your publisher report in the business portal shows the day's events and settled pairs.