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.
Most conversion events also advance the click's status on the Clicks page: Clicked → Landed → Started → Registered → Lead → Deposit → FTD. Status only moves forward - a late registration arriving after an ftd never downgrades the click. purchase is the exception: it's billed and reported like any other conversion, but it never touches click status. Neither do channel_join or the custom_* events - all three get recorded, they just don't feed the funnel-stage ladder.
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. Pull events back out withGET /api/events(paginated, filterable bytype) when you need to reconcile what fired against your own records. - 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.
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: Sent means every ad platform matched to the campaign accepted the conversion; Pending means at least one delivery is still queued or failing. 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. It fires a synthetic Purchase 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.
Common pitfalls
- Skipping
nonceon repeat conversions - the second deposit is silently treated as a retry and dropped. - Sending events without
payout- 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. - 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.