Events and conversions: the money layer of your funnel
Clicks are a cost until an event proves they paid off. In Leadgram every conversion - a bot start, a registration, a deposit - is an event tied to one specific click, and that link drives everything downstream: revenue in Reports, postbacks to partner trackers, the server-side conversions you feed back to Meta and Google, and a push notification back to the subscriber who converted, if your flow wires one up. Get events flowing correctly and the rest of the tracker starts telling you the truth about your spend.
Event types
- Funnel events -
click,land,bot_start,channel_join,join_request,first_dm,miniapp_launch. They describe how a user moves through your Telegram funnel. - Conversion events -
lead,registration,deposit,ftd,purchase. These are the events advertisers pay you for. - Custom events -
custom_1throughcustom_8for anything offer-specific. - Telegram Stars subscriptions -
subscription_renewedandsubscription_cancelled. They come from thesubscriptionupdate Telegram sends your bot: stateactive(the user re-enabled the subscription) becomessubscription_renewed, statescanceledandfailedbecomesubscription_cancelled. The recurring charge itself arrives separately, as apurchase.
The click-status ladder on the Clicks page is Clicked → Landed → Started → Registered → Lead → Deposit → FTD, and exactly six types move it: land → Landed, bot_start → Started, registration → Registered, lead → Lead, deposit → Deposit, ftd → FTD. Status only moves forward - a late registration arriving after an ftd never downgrades the click. Every other type leaves the status alone: click, channel_join, join_request, first_dm, miniapp_launch, purchase, custom_1-custom_8, subscription_renewed and subscription_cancelled are all recorded and reported, they just never touch the ladder. purchase is where that surprises people most: it's billed like any other conversion, and the click's status stays where it was.
How events arrive
- S2S / API. Your backend (or the advertiser's postback script) calls
POST /api/eventswith theclickId, the eventtype, and optionallypayoutandnonce. Create an API key under Settings → API keys for external tools like Keitaro or your own scripts. Once permissions have been saved on that key even once,eventshas to be among them - without it the conversion is refused with 403api_key_write_not_granted. A key that has never had permissions saved keeps posting conversions over HTTP exactly as before. Reading the data back is a different surface:GET /api/eventsis the dashboard screen and resolves a session only, so a key sent to it gets 401. What a key reads isGET /api/v1/clicks(the click feed, one row per click) andGET /api/v1/report(the funnel summary with counts), each once the matchingclicksorreportread permission is granted to that key - parameters, response shapes and status codes are in the API reference. - Flows. Drop an Event node into a Message Flow and the event fires automatically when a subscriber reaches that step - registrations and deposits straight out of your Telegram funnel, no code.
- Leadgram itself. A few types are emitted by the tracker from the Telegram updates your bot already receives. The next section says exactly which ones - and which ones will stay empty until you send them.
Deduplication is deterministic: the same clickId + type without a nonce is treated as a retry of the same conversion and never creates a second row, a second charge or a duplicate Meta purchase. A genuine repeat conversion (a second deposit from the same click) must send a unique nonce.
Which types Leadgram produces itself
Being listed in the Event node, mapped to a Meta event and accepted by the API does not mean a type is tracked for you. Three groups, and the difference decides whether you should be waiting on a number or sending it:
- Produced by Leadgram -
bot_start,channel_join,join_request,purchase,subscription_renewedandsubscription_cancelledcome out of the Telegram updates your bot already receives;miniapp_launchis emitted server-side, but only once the snippet from Mini Apps calls the launch endpoint - Telegram announces a Mini App launch to nobody. Connect the bot or the channel and the update-driven types start flowing on their own, with one condition: an event is written only when the update can be tied to a click. What can't be tied becomes no event at all and is counted under Unattributed drops on Delivery Health. That bites hardest onbot_start- someone who opens the bot by username instead of your tracking link never produces one. - Accepted from outside only:
click,land- the redirect writes a click row on the Clicks page, not an event, and nothing observes your landing page. A click's status therefore never reaches Landed unless you send alandevent yourself. - Accepted from outside only:
first_dm- Bot API webhooks only cover chats with your own bot; catching a first DM to a personal account needs an MTProto user session, which Leadgram does not run. Fire it from an Event node or post it toPOST /api/events. - Yours to send -
lead,registration,deposit,ftdandcustom_1-custom_8. Only your backend knows when the advertiser confirmed one.
Sending a type from the first group yourself is allowed and sometimes correct (your own billing reporting purchase, for instance), but it writes a second event next to the automatic one. bot_start is the exception: the webhook, an Event node and the API collapse onto one event per user, so reporting a start Leadgram already saw does not double it. That collapse is keyed on the pair (bot, Telegram user), which is why the API requires the user for this type - send it as telegram_user_id, tg_user_id, from.id or user.id inside payload. A bot_start without it is refused with 422 (bot_start_missing_telegram_user_id) rather than written as a second start that would reach Meta as a second Lead.
What a doubled start actually costs. Not money: you pay once per person per workspace - the charge fires on their first billable event, and a Telegram account is matched with no expiry - so a second bot_start from someone already paid for settles as free and your balance never sees it. The damage is downstream. The duplicate carries a different event_id, so Meta's own deduplication cannot merge it with the first one, and your campaign optimizer is taught that one person converted twice - you then pay for that lesson in every auction it bids in afterwards. The funnel counts in Reports drift by the same event. That is the reason for the pairing rules below, not a second invoice.
Two conditions come with that pairing, and both are worth stating plainly:
- Over the API, something has to name the bot. Send
bot_username(the @name) orbot_idinsidepayload, or report aclickIdwhose click carries a bot - clicks inherit the bot of their campaign, while a click from a plain redirect campaign has none, and neither does one whose bot was deleted. A report that names no bot either way cannot name the pair, so it is recorded as its own separate event instead of joining the shared one. Naming the bot explicitly also protects the multi-bot case: the click's bot is only a fallback guess, and it is the wrong one when the human started a different bot of yours. - An Event node joins the pairing only for flow runs that recorded the Telegram user. Runs started before this shipped did not, and their user cannot be reconstructed after the fact. For those the node emits nothing for
bot_startinstead of guessing - the webhook already recorded that start anyway. The node stays silent for another reason too: a flow run with no click behind it has nothing to attribute a start to, so instead of an event that could never reach Meta, the loss is counted under Unattributed drops on Delivery Health.
"Accepted from outside only" describes the code as it is, not a feature on the way. If nothing sends first_dm, no first_dm row will ever exist - being selectable in an Event node and mapped to Meta Contact does not make it automatic.
Retargeting: a second click for the same person
A retargeting re-click is the ordinary case: the same human clicks your ad again, gets a fresh click row, and starts the bot again. bot_start is counted once per bot and Telegram user, permanently, so the second start writes no new event, no new charge and no second Meta Lead. That is deliberate - the alternative is two Leads for one person, which corrupts the optimizer you are paying to train.
The cost of that choice is visible in one place: the new click keeps the status Clicked on the Clicks page even though the person did open the bot. Nothing advances it, because nothing new happened at the event level. When you measure a retargeting campaign, count starts from the events, not from click statuses.
The billing gate
Recording an event and delivering it downstream are two different things. Every event row carries a billing_status: it starts pending while the debit runs, then settles to charged or free. CAPI dispatch, postbacks and event-triggered pushes to the converting subscriber only fire once that status resolves to charged or free - a paused org or an empty balance leaves the row blocked (a failed debit leaves it error), and the event sits in the database without ever reaching Meta, Google, your postback endpoint or the subscriber.
If a conversion shows up in the events list but nothing fired on the other end, check billing before you check the integration. A blocked event isn't broken - it's doing exactly what it's supposed to.
The payout field
Pass payout (USD, up to two decimals) with every paying conversion. It feeds two places:
- Reports revenue - the revenue column is the sum of payouts across confirmed events.
- Google Ads - payout becomes
conversionValueon the server-side conversion, so Smart Bidding optimizes toward real money.
A conversion that arrives without a payout of its own inherits the Payout of the offer attached to the click: the platform looks the offer up by the offer_id stamped on the click. An explicit payout on the event always wins. A click that never passed through an offer has nothing to inherit, so the amount stays empty.
Postbacks don't carry payout. The macros available to partner trackers are {click_id}, {event_type}, the sub-chain and the platform-native click ids - see Postbacks for the full list. If your tracker needs the conversion amount, pass it back through your own integration.
What the CAPI column means
The Dashboard's Recent events table shows a CAPI status per event, and there are three of them. No attribution - the event has no click behind it; that check runs first, and neither CAPI nor a postback will ever fire for such a row. Sent - the current attempt has at least one successful delivery and no failed ones; skips don't count, so on a campaign that pairs Meta with Google a click carrying no gclid, gbraid or wbraid is skipped by Google as designed and the badge still reads Sent. Pending - everything else, including events with no deliveries at all: the campaign has no integrations, every platform skipped, or the billing gate never resolved to charged or free. Then the badge sits on Pending with an empty queue. Per-platform detail - attempts, last error, one-click re-fire - lives on Delivery Health.
The safe test event
On the Facebook page each Meta CAPI account has a Send test event button. You pick the event name: the dropdown offers eight - Lead, SubmitApplication, Subscribe, CompleteRegistration, Contact, ViewContent, PageView, Purchase - and the default is Lead, what bot_start maps to. The event goes out synthetic, with your Test Events code from Meta Events Manager, so it shows up in the Test Events tab and never touches ad optimization, attribution or billing. It's the fastest way to prove your pixel and token work before spending a dollar. The button refuses to fire without a code - an uncoded event would count as real. Pick the name your funnel actually produces: you set the campaign objective from what you saw in Test Events, and testing Purchase on a funnel that sends Lead leaves Ads Manager blank.
Common pitfalls
- Skipping
nonceon repeat conversions - the second deposit is silently treated as a retry and dropped. - Sending events without
payouton clicks with no offer to inherit one from - Reports revenue stays at zero and Google gets valueless conversions. - Firing
registrationfor a click that already reachedftdand expecting the status to change - it won't, by design. - Expecting
purchaseto move the click's funnel status - it never does, even though it's billed like any other conversion. - Reading Pending in the CAPI column as a bug - check Delivery Health first; it usually names the exact platform error.
- Assuming a recorded event was delivered -
blocked,errorandpendingbilling states mean it never reached CAPI, postbacks or pushes. - Reconciling with
GET /api/eventsand an API key - that endpoint answers a dashboard session only and returns 401 to a key; reconcile throughGET /api/v1/clicksandGET /api/v1/report. - Judging a retargeting campaign by click statuses - a re-click by someone who already started the bot stays at Clicked by design, because the start is counted once per person.
- Waiting for
miniapp_launchto appear on its own - Telegram sends no launch update; the number stays at zero until the Mini Apps snippet is live.