tellmetric
Guide

Telegram Mini App attribution: which ad paid for each user, proven by Telegram

A Mini App runs inside Telegram, so the ad click, the open and the purchase happen in three different places and nothing connects them by default. This guide shows how the click id travels into the app through the start parameter, how Telegram's signed initData proves who sent each event, and how a purchase inside the app reaches Meta or TikTok with its value.

What Mini App analytics usually measure, and what they miss

Most teams running a Mini App already have some analytics in it: screens opened, buttons tapped, sessions, retention by day. Those tools answer "what do users do inside the app?". They do not answer "which ad paid for this user?", because the ad click happened in a browser or in the Telegram client before the app existed for that person, and the app only sees a Telegram user id. Retention by cohort tells you that Tuesday's users stayed; it cannot tell you that Tuesday's users came from creative B on TikTok and that creative B's users pay twice as often as creative A's.

Attribution is the other half: tying the Telegram user, and everything they go on to do in the app, back to the click, so that the join, the first action and the purchase land on the right campaign, ad set and creative, and so that the ad platform can be told about the purchase and optimise for buyers. The two halves do not compete. Keep the product analytics for the inside of the app; add attribution for the outside.

How the click id reaches the Mini App

Telegram lets a Mini App be opened with a parameter: t.me/<bot>/<app>?startapp=<value>. Whatever is in startapp arrives inside the app as start_param, and Telegram includes it in the signed initData it hands the page. That parameter is where the click id goes.

  1. The ad points at a tracking link. On the Links page, create a link of the kind "Opens a web page" and set its destination to your Mini App address with the click id macro: https://t.me/yourbot/app?startapp={clickid}. Name the link after the ad; the ad URL carries utm_campaign from the name and the platform's own parameters (fbclid, ttclid, gclid) at click time.
  2. The click is recorded with its own id, the platform click id, the browser cookies when the click passed through a page with the snippet, UTM parameters, sub1 to sub9, country and device. The macro is filled with that id and the visitor is sent to Telegram, which opens the Mini App.
  3. The app reads nothing by hand. It does not need to parse the URL or keep the click id in storage. The click id sits inside initData, signed by Telegram, and travels with every event the app posts.

The same link works for a Telegram Ads campaign, where there is no platform click id: the row is attributed per creative, as described in Telegram Ads conversion tracking.

Posting an event from the app

The Mini App sends one request per event to the Mini App endpoint. Two fields matter: site_key, the public website key shown on Connections and on the developer page (the same key the tracker.js snippet uses; it is safe in a browser), and init_data, the string Telegram exposes as window.Telegram.WebApp.initData, passed through untouched.

POST /miniapp/event
POST https://www.tellmetric.com/miniapp/event
Content-Type: application/json

{
  "site_key": "sk_YOURKEY",
  "init_data": "<window.Telegram.WebApp.initData>",
  "event_name": "Purchase",
  "value": 29.00,
  "currency": "EUR",
  "event_id": "order-18233"
}
  • event_name defaults to Lead. Use Purchase for a payment, CompleteRegistration for a sign-up, or your own name: letters, digits, spaces, underscores or hyphens, up to 32 characters.
  • value and currency (three-letter ISO code) are optional for anything that is not a payment. For a purchase, send both: that is what reaches Meta and TikTok.
  • event_id is optional and is your deduplication key. Without it, the id is built from the event name, the Telegram user and the click id, so a second Lead from the same person on the same click is ignored. For purchases, always send your order id, otherwise a second purchase by the same person would be dropped as a duplicate.
  • The response is 202 with accepted, the event_id stored and matched_click, which tells you whether the app was opened with a click id Tellmetric knows. 401 means the site key is unknown, no bot is connected to the workspace, or the signature did not verify.
  • Refunds and chargebacks are refused here. Negative events go through the postback from your backend, where they are tied to the original purchase.

The endpoint answers CORS preflight requests, so the call can come straight from the app's page. The request formats and an interactive API reference are on the developer page for logged-in accounts, on every plan.

How the server proves who sent the event

Anyone can POST JSON to a public endpoint, so a Telegram user id in the body would prove nothing. initData solves this. When Telegram opens a Mini App, it signs the launch data (the user, the start parameter, the time) with a key derived from the bot's token, HMAC-SHA256. Tellmetric holds that bot token, because you connected the bot on Connections, so it recomputes the signature and compares. If it matches, three facts are proven at once: the event came from a real Mini App launch, the Telegram user is who the data says, and the start parameter, the click id, was really the one the app was opened with.

The result is a (Telegram user id, click id) pair that cannot be forged by a script, a competitor or a bored user editing a request. Launch data older than 24 hours is refused, so a captured initData cannot be replayed indefinitely. The pair is then used like any other attribution: the event inherits the click's campaign, ad set, creative, platform click id and cookies, and carries the hashed Telegram user id as external_id.

If the app was opened without a start parameter, say from the bot's menu button or a forwarded link, the event is still recorded with the hashed user id, but matched_click is false and it has no ad behind it. The Mini App endpoint does not fall back to the person's group join; that lookup exists for Stars payments recorded by the bot, not for app events. Keep the startapp link on every ad and the unattributed share stays small.

Purchases inside the app, reported to the ad platform

The reason to attribute at all is the purchase. A Purchase event with value and currency is delivered to Meta through the Conversions API as a Purchase, to TikTok as CompletePayment, and to Snapchat, Reddit, Spotify, X and Google Ads as their equivalents (those six platforms are on the Pro and Agency plans; Meta is on every plan). The event carries the fbclid or ttclid from the click and the hashed user id, so the platform matches it to the ad and the campaign can be set to optimise for purchase value instead of link clicks.

Meta refuses events older than seven days, so post the purchase when it happens, from the app or, if you prefer to trust only your backend, from the server through the event API with the same click id. Both paths dedupe on the event id, so sending the same order from both is harmless.

Payments made with Telegram Stars inside the bot, rather than through your own checkout, do not need the app to post anything: the bot records them from Telegram's successful_payment update. See Telegram Stars purchase tracking.

A worked example

The figures are illustrative. A language-learning Mini App runs two Meta ad sets and one TikTok ad group, each with its own startapp link, for a month:

  • Meta ad set A: €1,000 spent, 2,500 opens, 1,900 Leads (the app posts a Lead on first open), 60 purchases worth €1,740. Cost per Lead €0.53, cost per purchase €16.67, return 1.74.
  • Meta ad set B: €1,000 spent, 4,100 opens, 3,300 Leads, 22 purchases worth €640. Cost per Lead €0.30, cost per purchase €45.45, return 0.64.
  • TikTok ad group: €1,000 spent, 3,000 opens, 2,100 Leads, 48 purchases worth €1,390. Cost per Lead €0.48, cost per purchase €20.83, return 1.39.

The product analytics tool reports 9,600 new users and a day-7 retention of 31%, which is true and says nothing about where to put next month's budget. The attribution rows do: B is the cheapest user and the worst buyer, and after both Meta ad sets switch to optimising for Purchase value, Meta starts to prefer A's kind of user. A month later B's cost per purchase has come down, because Meta now knows what a purchase looks like.

Where it breaks, and how to tell

  • The ad links straight to t.me/yourbot/app. No click is recorded and start_param is empty; every event has matched_click: false. Use the ad URL of a Links page link whose destination contains {clickid}.
  • The app modifies initData. Parsing it into an object and re-serialising changes the string and the signature no longer verifies; the endpoint answers 401. Pass the original string.
  • Testing in a normal browser. Outside Telegram there is no initData, so nothing can be verified. Test from inside the Telegram client, on a phone or desktop.
  • Purchases without an event_id. The second order from the same person on the same click is treated as a repeat of the first. Send the order id.
  • A currency that is not an ISO code. Meta rejects the event; the Delivery tab shows the reason.
  • The bot token changed. The signature is checked against the token on Connections; after a token reset in @BotFather, paste the new one or every event fails verification.

Frequently asked

Does the Mini App need to know anything about the ad?

No. The click id arrives in the signed launch data when the app is opened through the tracking link. The app posts its events with the launch data and Tellmetric does the rest.

Can someone fake an event or a click id?

Not without the bot's token. Telegram signs the launch data, including the start parameter, and the server verifies the signature before recording anything.

Which platforms receive in-app purchases?

Meta on every plan; TikTok, Snapchat, Reddit, Spotify, X and Google Ads on Pro and Agency, each as its purchase event with value and currency.

Does this replace my product analytics?

No. Keep it for screens, funnels inside the app and retention. Attribution adds the ad behind each user and reports purchases to the platforms.

What about refunds?

Send them from your backend through the postback with the refund status. They are tied to the original purchase and never sent to an ad platform.

Related guides

Track Telegram and Discord conversions from your ads

The Free plan requires no card. Upgrade to Pro at any time.