Skip to content
Open an account Sign in
Offerwall 6 pages

Offerwall

Signed URLs

HMAC on the wall URL — when you need it and how to compute it.

On this page 4 sections
  1. The signature
  2. Compute it
  3. Rules
  4. Verifying you got it right

By default anyone who knows your App ID can load your wall with any userid. For most publishers that is harmless: the wall shows offers, and rewards only move when a real conversion completes.

It stops being harmless when your userid is guessable. If your identifiers are sequential integers, anyone can open a wall as user 1, complete an offer, and the reward lands on someone else's account.

Turn on Require signed wall URLs in Apps → Security when your user identifiers are enumerable.

The signature#

sig = sha256(appid + userid + secret_key)

Plain concatenation, no separators, lower-case hex. Computed on your server, then written into the iframe URL you render.

With the app active and signing required, a request with a missing or wrong sig gets a 403 and no offers.

Compute it#

<?php

$appId  = 'YOUR_APP_ID';
$userId = (string) $user->id;

$sig = hash('sha256', $appId . $userId . getenv('ADNUVORA_SECRET'));

$url = 'https://adnuvora.ziadt.dev/wall?' . http_build_query([
    'appid'  => $appId,
    'userid' => $userId,
    'sig'    => $sig,
]);
import { createHash } from 'node:crypto';

const appId  = 'YOUR_APP_ID';
const userId = String(user.id);

const sig = createHash('sha256')
  .update(appId + userId + process.env.ADNUVORA_SECRET)
  .digest('hex');

const url = `https://adnuvora.ziadt.dev/wall?appid=${appId}&userid=${encodeURIComponent(userId)}&sig=${sig}`;
import hashlib, os
from urllib.parse import urlencode

app_id  = "YOUR_APP_ID"
user_id = str(user.id)

sig = hashlib.sha256(
    (app_id + user_id + os.environ["ADNUVORA_SECRET"]).encode()
).hexdigest()

url = "https://adnuvora.ziadt.dev/wall?" + urlencode(
    {"appid": app_id, "userid": user_id, "sig": sig}
)

Rules#

  • Never compute the signature in the browser. Shipping the secret to the client defeats the entire mechanism and leaks the key that also signs your postbacks.
  • Sign the exact string you send. If you URL-encode userid, sign the decoded value — we verify against what we decoded.
  • The signature is not a session. It authenticates the pairing of an app and a user identifier. It does not expire and it carries no timestamp; a user who bookmarks their wall URL keeps a working one.
  • Rotating the secret invalidates every signed URL you have already rendered, and every postback signature your endpoint is checking. Rotate during a deploy, not on a Friday.

Verifying you got it right#

Turn signing on, load your wall without sig, and confirm you get a 403. Then load it with the signature and confirm the offers come back. If both hold, you are done.