Add an Offerwall to a Telegram Mini App

Key takeaways

  • A Mini App is your own web app inside Telegram, so your backend signs the offerwall link and credits rewards, as on any website.
  • Trust only initData that your server validated with the bot token, never initDataUnsafe sent by the client.
  • Read Telegram's Bot Platform Developer Terms yourself: we found no rule that names offerwalls, but several rules apply to how you run one.

How do you add an offerwall to a Telegram Mini App?

To add an offerwall to a Telegram Mini App, send the Mini App’s initData to your backend, validate it there with your bot token to get the Telegram user, map that user to an account of your own, and build the signed offerwall link on the server. The Mini App then opens the link with Telegram.WebApp.openLink when the user taps a button, and your backend credits rewards when the offerwall sends a postback.

For Telegram Mini App monetization this is the same pattern as a rewards website, with Telegram handling sign-in. If you run a channel rather than a Mini App, How to make money with a Telegram channel covers Telegram’s own programs and a companion earn page.

What a Mini App is, and what that means here

A Telegram Mini App is a web page that Telegram opens inside its apps, connected to your bot. It is hosted on your own server, loads Telegram’s telegram-web-app.js script, and receives launch data about the user from Telegram. Three facts from Telegram’s Mini Apps documentation shape the integration:

  • initData is the raw launch data, a query string meant for validation on your server.
  • initDataUnsafe should not be trusted. Telegram’s docs say to use data from initData only on the bot’s server, after validating it.
  • openLink opens a link in an external browser, and the Mini App is not closed. It can only be called in response to a user interaction, such as a tap inside the Mini App.

The offerwall itself does not change. Your backend is still the place that knows who the user is, signs the link, and owns the balance.

The flow at a glance

StepWhereWhat happens
1Mini AppSends Telegram.WebApp.initData to your backend
2Your backendValidates it with the bot token, finds or creates the user’s account
3Your backendBuilds the signed offerwall link and returns it
4Mini AppOpens the link with openLink when the user taps Earn
5Sharklio to your backendSigned postback when a result is credited or reversed
6Mini AppReloads the balance from your backend

On Sharklio, use an app of the Direct link type: the link opens in any browser, with no website restriction.

Step 1: validate initData on your server

Telegram documents the check: build a data-check-string from all received fields except hash, sorted alphabetically as key=value lines joined with a line feed. The secret key is the HMAC-SHA256 of your bot token with the constant string WebAppData as the key. The hex HMAC-SHA256 of the data-check-string with that secret must equal hash. In PHP:

function telegram_user_from_init_data(string $initData, string $botToken): ?array
{
    parse_str($initData, $fields);
    $hash = $fields['hash'] ?? '';
    unset($fields['hash']);
    ksort($fields);
    $lines = [];
    foreach ($fields as $k => $v) {
        $lines[] = $k . '=' . $v;
    }
    $secret = hash_hmac('sha256', $botToken, 'WebAppData', true);
    $check = hash_hmac('sha256', implode("\n", $lines), $secret);
    if (!is_string($hash) || !hash_equals($check, $hash)) {
        return null;
    }
    if (time() - (int) ($fields['auth_date'] ?? 0) > 86400) {
        return null;
    }
    $user = json_decode($fields['user'] ?? '', true);
    return is_array($user) && isset($user['id']) ? $user : null;
}
  • The bot token stays on the server. It is a credential, and Telegram’s developer terms prohibit making such credentials public.
  • Check auth_date. Telegram suggests it to reject outdated data. A day is used above; pick what fits your app.
  • Never accept a user ID the client sends separately. Only the user object inside validated initData counts.

Look up the account that belongs to the Telegram user ID, or create one on first launch, and use your own account ID as the offerwall user ID. It never changes, it carries no personal data, and if the same person also uses your website, both can share it. Then build the link exactly as on any website:

$tgUser = telegram_user_from_init_data($_POST['initData'] ?? '', TELEGRAM_BOT_TOKEN);
if ($tgUser === null) {
    http_response_code(403);
    exit;
}
$account = find_or_create_account_for_telegram((int) $tgUser['id']);
$userId = (string) $account->id;
$hash = hash_hmac('sha256', $userId, SHARKLIO_LINK_SALT);
header('Content-Type: application/json');
echo json_encode([
    'url' => 'https://wall.sharklio.com/' . SHARKLIO_APP_ID . '?user_id=' . rawurlencode($userId) . '&hash=' . $hash,
]);

find_or_create_account_for_telegram() stands for your own user storage. The hash is an HMAC-SHA256 of the user ID with your link hash salt, which also stays on the server. The Offerwall link docs have the same code in Node.js and Python.

Step 3: open the offerwall from the Mini App

Fetch the link when the earn screen loads, and open it in the tap handler, because openLink only works in response to a user interaction:

<script src="https://telegram.org/js/telegram-web-app.js"></script>
<button id="earn" disabled>Earn coins</button>
<script>
const tg = window.Telegram.WebApp;
let wallLink = null;
fetch('/api/offerwall-link', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({ initData: tg.initData }),
})
  .then((r) => r.json())
  .then((d) => {
    wallLink = d.url;
    document.getElementById('earn').disabled = false;
  });
document.getElementById('earn').addEventListener('click', () => {
  if (wallLink) {
    tg.openLink(wallLink);
  }
});
</script>

The offerwall then opens in the user’s browser, where offers that lead to app stores, sign-ups, and screenshot uploads behave as they do on any website. The Mini App stays open in Telegram behind it.

What about showing it inside the Mini App?

An iframe of the offerwall only loads on https pages of the website registered for the app and its subdomains, and browsers apply that rule to every parent frame, not just the closest one. Telegram’s web clients run Mini Apps inside a frame of their own page, so an embedded offerwall can fail to load there even when it works in other clients. If you want it embedded, register the Mini App’s domain with an app of the Offerwall type and test every Telegram client you support. openLink avoids the question entirely, which is why we suggest it.

Step 4: credit rewards through your backend

Rewards never pass through the Mini App. When a result is final, Sharklio calls your postback URL. Your server checks the hash, an HMAC-SHA256 of transaction ID, user ID, reward, and status joined with colons and signed with your postback secret key, then:

  • adds the reward on status 1, once per transaction ID,
  • takes it back on status 2, only if that transaction was credited,
  • shows status 3 (pending, opt-in) as on its way, and credits nothing on status 4,
  • answers with a 2xx status within 6 seconds, with no redirect.

When the user is back in the Mini App, load the balance from your backend each time the earn screen is shown, and add a refresh button. If your bot is allowed to message the user, a short note after a credit is a nice touch; keep it to real events, because Telegram’s developer terms forbid spamming users with unsolicited messages. The handler rules are explained in Postback security: verify every reward call, and the concept in What is a postback URL?

Telegram’s rules to check before launch

We read Telegram’s Bot Platform Developer Terms and Terms of Service for Mini Apps for this guide. We did not find a clause that names offerwalls or rewarded offers, either allowing or forbidding them, so do not read this section as approval from Telegram. Read the terms yourself, because Telegram can change them and can remove a Mini App that it decides breaks them. These parts clearly apply; the section numbers are those of the Bot Platform Developer Terms:

  • Privacy policy. Every bot and Mini App must have an accessible privacy policy saying what data it stores, how, and why. Telegram’s Standard Privacy Policy applies if you set none, but if it does not describe what you do, you must provide your own in @BotFather. An offerwall means data such as IP address, device, and offer activity reaches the offerwall provider, so say so.
  • Required security checks. The terms ask developers to use the documented security checks and to protect their app from malicious clients, naming arbitrary data in WebAppData as an example. Validating initData as in Step 1 is that check.
  • No misleading users. Section 5.2 forbids misrepresenting what a Mini App provides, including misleading users into actions in pursuit of an unachievable outcome, and lists examples such as MLM or ponzi schemes and social growth manipulation. Describe rewards honestly, and if social tasks do not fit your audience, hide the social category in your app’s Offers tab.
  • Digital goods are paid in Stars. Section 6.2 says digital goods and services sold in a Mini App must be paid with Telegram Stars. Earning through offers is not a sale, but if you also sell coins or boosts, that rule applies.
  • Crypto must be TON-based. Section 7 requires Mini Apps with cryptocurrency features to use the TON blockchain only, and section 7.4 forbids directing users to external platforms where cryptoassets not based on TON are promoted or used. If you pay rewards in crypto, or an offer could fall under that rule, read section 7 and Telegram’s blockchain guidelines first.

Sharklio’s own rules apply too: end users must be at least 16, the offerwall opens only when the user taps, and incentivized clicking outside the offerwall is not allowed. The traffic quality rules list the rest.

Step 5: test it end to end

  1. Open the Mini App from a test Telegram account and check your server log: validation should pass, and a request with a changed initData should get a 403.
  2. Compare the link your server returns with Test a link in the Integration tab for the same user ID.
  3. Tap Earn on Android, iOS, and desktop, and confirm the offerwall opens in the browser.
  4. Use Send a test postback in the Postback tab for your test account’s ID with status 1, and check that the balance goes up once. Opening the same postback URL again from Reports, Postbacks must not change it. A status 2 test gets its own new test_ transaction ID, which you never credited, so your handler must ignore it.

More checks are in the Testing and troubleshooting docs.

A Sharklio offerwall behind your Mini App’s Earn button

A Sharklio app of the Direct link type gives your Mini App one signed link per user, your own currency name, icon, colors, and rate, and signed postbacks for every credit and reversal. There is no SDK, so the whole integration is the server code above and one button. Publisher applications open soon. Read how to get approved as a publisher, and compare the other ways to show the wall in Offerwall integration: iframe, link or API?

Frequently asked questions

Can I use initDataUnsafe to get the Telegram user ID?

Not for anything that touches rewards. Telegram’s docs say that data should not be trusted. Send initData to your server and validate it with your bot token.

Does Telegram allow offerwalls in Mini Apps?

We found no rule in Telegram’s developer or Mini App terms that names offerwalls either way. Rules on privacy, misleading users, Stars payments, and crypto do apply, so read the current terms before launch.

Why use openLink instead of an iframe?

openLink opens the offerwall in the user’s browser, where store links and uploads behave normally. An iframe only loads on the registered website, and Telegram web clients that frame the Mini App can stop it from loading.

Can the Mini App credit the reward itself?

No. Anything in the client can be changed. Only your backend credits users, from postbacks whose hash it verified.

Which user ID should I send to the offerwall?

Your own account ID for that Telegram user. It stays the same, carries no personal data, and can be shared with your website.