Wire it up once, earn everywhere.
Load the wall in an iframe or a mobile WebView with your App ID and a user ID. We verify every reward, credit the balance, and can notify your own server too.
<iframe src="https://wall.goolarr.com/?app_id=YOUR_APP_ID&user_id=USER_ID" width="100%" height="800" frameborder="0" style="border:0;border-radius:8px" />
Pick the surface you already have.
Three pieces, one flow.
Your App ID, the wall, and your postback endpoint all work together without any extra plumbing.
Get your App ID
Create a project in the dashboard. Each one gets its own App ID and Server Key.
Load the wall
Point an iframe or WebView at wall.goolarr.com with your App ID and a stable user ID.
Verify and credit
We credit the Goolarr balance automatically. If you want your own currency updated too, verify the signed postback we send your server.
Embed it in one tag.
Point an iframe at the wall with your App ID and a user ID. Branding, currency, and layout all come from your project settings automatically.
app_idYour project's App ID, from the dashboard. Required.
user_idA stable, unique ID for the signed-in user on your platform. Required.
<iframe src="https://wall.goolarr.com/?app_id=YOUR_APP_ID&user_id=USER_ID" width="100%" height="800" frameborder="0" style="border:0;border-radius:8px" allow="payment; fullscreen" ></iframe>
<div style="position:relative;width:100%;padding-bottom:75%;height:0">
<iframe
src="https://wall.goolarr.com/?app_id=YOUR_APP_ID&user_id=USER_ID"
style="position:absolute;inset:0;width:100%;height:100%"
frameborder="0"
allow="payment; fullscreen"
></iframe>
</div>Same URL, native shell.
The wall is just a signed URL, so any platform that can render a WebView can host it.
<iframe src="https://wall.goolarr.com/?app_id=YOUR_APP_ID&user_id=USER_ID" width="100%" height="800" frameborder="0" style="border:0;border-radius:8px" allow="payment; fullscreen" ></iframe>
Every reward, verified.
We credit the Goolarr balance the moment a reward is verified, whether or not you set anything up here. If you also want to update your own in-app currency, set a callback URL in your project's Integrations tab and we'll forward a signed copy of every event to it.
Reward Callback URL
Called on COMPLETE and SCREENOUT, so you always know a session finished, credited or not.
Reconciliation Callback URL
Called on RECONCILIATION, when an earlier reward is adjusted after the fact — a refund or chargeback.
Server Key
Signs every callback we send you. Find it next to your callback URLs in the Integrations tab.
Example request your server receives
uidYour user's ID, formatted as YOUR_APP_ID--user_id. Split on the double hyphen to get back the ID you passed in.
Every eventtxUnique transaction ID. Store it — a retried postback should never credit twice.
Every eventtypeCOMPLETE, SCREENOUT, or RECONCILIATION.
Every eventvalReward amount in your app's currency. Zero on a screenout.
COMPLETE, RECONCILIATIONrawRaw USD value before your currency conversion.
COMPLETE, RECONCILIATIONreasonWhy the user didn't qualify.
SCREENOUT onlyrefThe original transaction ID being adjusted.
RECONCILIATION onlyrat, loi, cat, countrySurvey rating, length, category, and user country.
COMPLETE onlyHow the signature works
We take the full callback URL with every parameter set, sign it with your Server Key using HMAC-SHA1, and append the result as hash. To verify it, rebuild the same URL from what you received, strip the hash param, sign it yourself, and compare.
import crypto from 'crypto';
function isValidCallback(fullUrl: string, serverKey: string): boolean {
const url = new URL(fullUrl);
const receivedHash = url.searchParams.get('hash');
if (!receivedHash) return false;
url.searchParams.delete('hash');
const expected = crypto
.createHmac('sha1', serverKey)
.update(url.toString())
.digest('hex');
const a = Buffer.from(receivedHash);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express handler for your Reward Callback URL
app.get('/reward-callback', (req, res) => {
const fullUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
if (!isValidCallback(fullUrl, process.env.GOOLARR_SERVER_KEY!)) {
return res.status(401).send('INVALID_HASH');
}
const { uid, tx, type, val } = req.query;
const [appId, userId] = String(uid).split('--');
if (await alreadyProcessed(tx as string)) {
return res.status(200).send('OK'); // already credited, ignore
}
if (type === 'COMPLETE') {
await creditUser(userId, Number(val));
}
await recordTransaction(tx as string);
res.status(200).send('OK');
});Got a stack we didn't cover? We'll help you wire it up.