
Publisher docs
Integrate your app with 
The same integration guide publishers see inside the dashboard.
Embedding the wall
The hosted link, the iframe and the one-tag script embed.
Hosted link
Send users to your hosted LootHQ offerwall by URL. It is the one integration with no code in it at all: no JavaScript and no iframe, so it survives webviews and email clients. Tracking, device detection and localization happen on our side.
https://www.loothq.com/wall?placement_id=YOUR_PLACEMENT_ID&sub_id={USER_ID}Required query parameters
| Param | Description | Example |
|---|---|---|
| placement_id | The placement ID from your publisher dashboard → Placements. Each placement represents one integration point (app, site, etc). | placement_id=64f3b... |
| sub_id / user_id | Stable user identifier from your system, used to attribute conversions to the correct user. sub_id and user_id are interchangeable. Must be unique across ALL your placements, not just within one. See the note below. | sub_id=USER_987 |
Use the same identifier for both the offerwall (subID) and your postback handler so that conversions always credit the right account.
sub_id must be unique across ALL of your placements, not just within one. If you run multiple apps or sites, don't reuse a per-app numeric ID (e.g. a plain database row ID) as-is across them. Two different placements handing us the same sub_id looks like the same person to us and merges their coin balances and history. Prefix or namespace your IDs per placement (e.g. app1_4821 vs. app2_4821) if there's any chance of collision.
iFrame
Use an iFrame to keep users inside your own layout while LootHQ handles the offerwall content.
<iframe
id="loothq-wall"
src="https://www.loothq.com/wall?placement_id=YOUR_PLACEMENT_ID&sub_id={USER_ID}"
width="100%"
height="700"
frameborder="0"
scrolling="no"
allow="clipboard-write"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
style="border: none; width: 100%; min-height: 700px; border-radius: 12px;"
></iframe>
<script>
window.addEventListener('message', function (e) {
var frame = document.getElementById('loothq-wall');
// Only act on messages that actually came from the wall iframe you
// embedded: check e.origin before touching e.data. Never trust a
// postMessage's origin implicitly, and never reply with '*'.
if (!frame) return;
var wallOrigin;
try { wallOrigin = new URL(frame.src, window.location.href).origin; } catch (err) { return; }
if (e.origin !== wallOrigin) return;
if (!e.data) return;
// The wall asks whether a host will open offers on its behalf. Answer, or
// it opens them itself, which your sandbox may block.
if (e.data.type === 'loothq:wall-ready' && e.source) {
e.source.postMessage({ type: 'loothq:host-ready' }, wallOrigin);
}
// Auto-resize iframe to fit wall content
if (e.data.type === 'loothq:wall-height' && frame) {
frame.style.height = Math.max(e.data.height, 500) + 'px';
}
// Open offer links in a new tab (required when iframe has sandbox restrictions)
if (e.data.type === 'loothq:open-url' && e.data.url) {
window.open(e.data.url, '_blank', 'noopener,noreferrer');
}
});
</script>
<!-- https://www.loothq.com/wall.js does all of the above, and the modal, in one tag.
See the Script section. -->We recommend a dedicated "Earn" or "Offerwall" page with a minimum height of 650–800px. You can wrap the iFrame in your own navigation and branding.
If your site sends a Content-Security-Policy header
You must allow LootHQ in your frame-src directive, or the browser will refuse to load the wall and your users will see "This content is blocked. Contact the site owner":
Content-Security-Policy: frame-src https://www.loothq.com;
This failure happens entirely inside the browser. It never reaches our servers, so it won't appear in your LootHQ logs or ours. An empty space where the wall should be, with a CSP violation in the browser console, is the signature.
What your users see in the address bar
The address bar always shows your site. That is how iframes work, and it is the point: the wall is part of your product, not a redirect away from it. The only LootHQ-branded URL in the whole flow is the sign-in popup, and that is deliberate. Credentials are only ever entered on a window whose address bar visibly reads loothq.com, so a user can always tell a genuine LootHQ sign-in from a page imitating one. We never ask for a password inside the iframe.
Script
One tag, and we manage the frame. The script mounts the same canonical wall URL as the iFrame method, then does the plumbing the hand-rolled version asks you to write yourself. This is the embed the Quickstart recommends.
<div id="loothq-wall"></div>
<script
src="https://www.loothq.com/wall.js"
data-placement-id="YOUR_PLACEMENT_ID"
data-sub-id="{USER_ID}"
data-container="loothq-wall"
></script>
<!-- Replace YOUR_PLACEMENT_ID with the id from Dashboard → Placements, and
render {USER_ID} server-side as the signed-in user's STABLE id. -->Attributes
| Attribute | Description |
|---|---|
| data-placement-id | Required for the inline mount. A tag without it mounts nothing and only installs the window.LootHQ API (with its legacy alias and message listener). That is the modal-only usage below, not an error. |
| data-sub-id | Your stable id for the signed-in user (alias: data-user-id). Same rules as sub_id on the wall URL: stable, and unique across all your placements. |
| data-container | Id of the element to mount into (alias: data-container-id). Defaults to loothq-wall; if no such element exists, the script creates a <div> right after the script tag. |
| data-base-url | Override the wall origin (alias: data-origin). Defaults to the origin the script was loaded from; you almost never need this. |
What it handles for you
- Auto-resize. Listens for the wall's height messages and sizes the frame to fit (the inline mount keeps a 600px minimum), so there's no clipped, unscrollable wall to debug.
- The ready handshake. Answers the wall's
wall-readyannouncement withhost-ready, which tells the wall to delegate offer-opening to the page. Without a host, the wall opens offers itself, which works only as long as your sandbox allows popups; a stricter sandbox (noallow-popups) silently swallows the click. The script never depends on your sandbox choices. - Offer opening. Opens each offer in a new tab with
noopener,noreferrer, and dedupes the wall's double-fired click messages so one click never opens two tabs.
What the script does on your page
The answer for your security review: it reads its own data- attributes, creates iframes pointed only at https://www.loothq.com (one per inline mount or modal open), installs window.LootHQ (plus a legacy window.RewardsRiver alias), and registers a single postMessage listener. That listener checks e.origin and accepts messages only from origins of frames the script itself created (normally exactly https://www.loothq.com), and every reply is targeted at that origin, never "*". It sets no cookies, and apart from looking up its mount container (plus an Escape-key listener while the modal is open) it reads nothing else from your page. Rewards are server-to-server via postbacks, so the script exposes no events or callbacks to the host page; there is nothing else to wire up.
Modal mode
For the wall as an overlay rather than a page, load the tag with no data-placement-id and open it on demand:
<script src="https://www.loothq.com/wall.js"></script>
<script>
// e.g. from your "Earn coins" button:
document.getElementById("earn-btn").addEventListener("click", function () {
LootHQ.open("YOUR_PLACEMENT_ID", getCurrentUserId());
});
</script>LootHQ.open(placementId, subId) shows the wall in a full-screen overlay; Escape, a click on the backdrop, or the ✕ closes it, and LootHQ.close() closes it programmatically. Same wall, same attribution, same CSP requirements as the inline mount.
LootHQ.openOffer(detailUrl) opens one offer's detail card in the same overlay, from the detailUrl the Offers API feed returns per offer on placements with the offer detail embed switched on (ask us). The frame closes the overlay itself once the user starts the offer.
If your site sends a Content-Security-Policy header
The tag loads JavaScript from our origin and mounts an iframe pointed at it, so a strict CSP needs script-src as well as frame-src:
Content-Security-Policy: script-src https://www.loothq.com; frame-src https://www.loothq.com;
Like the iFrame case, this failure happens entirely inside the browser and never reaches our servers, so it appears in neither your LootHQ logs nor ours. The signature is a CSP violation in the browser console and no wall.
When to pick which: iFrame means you manage the frame: sizing, placement, and the short message listener. Script means we manage the frame: the tag mounts it, sizes it, and handles offer opening, with modal mode when you want an overlay. Both render the same wall from the same canonical URL.
Setting the wall URL after login
A companion pattern for the iFrame method, not an integration method of its own: leave the iframe's src unset and fill it in client-side once you know who the user is.
const userId = getCurrentUserId(); // your logic
const placementId = "YOUR_PLACEMENT_ID"; // from publisher dashboard → Placements
const wallUrl = "https://www.loothq.com/wall"
+ "?placement_id=" + encodeURIComponent(placementId)
+ "&sub_id=" + encodeURIComponent(userId);
document.getElementById("loothq-wall").src = wallUrl;Useful if you render your app client-side, or if the wall URL is not known until after authentication.