Integrate your app with XTRACKER in 10 minutes
Step-by-step: create a link, get your API keys, send canonical events from Python or curl, and fire idempotent postbacks. Includes a drop-in async client.
This is the hands-on guide to wiring your bot/app to XTRACKER. By the end you'll be sending subscribed, registration and deposit events that show up live on your dashboard.
You'll touch four screens, in this order:
1. Create a link and grab your keys
In the dashboard, create a link for your funnel:

Then open API keys for the space and rotate to reveal your secrets:

You'll see two secrets:
- Bearer — authenticates the write API (
POST /events,/bind,/clicks). Keep it server-side only. - Ingest token — for header-less
GET /ecalls (postbacks). It travels in URLs, so it's lower-trust — use it only where you can't set a header.
Both are shown once when you rotate them. Save them in your secrets manager.
The keys page also renders ready-to-paste code and postback URLs with your real tokens filled in. This article is the narrative version.
2. Understand the uid
Every event is tied to a uid — the click id the user arrived with. You don't generate it: the landing mints it and passes it in the deeplink t.me/<bot>?start=fb_<uid>. In your /start handler, read the payload and strip the fb_ prefix:
payload = message.text.partition(" ")[2] # "fb_abc123"
uid = payload[3:] if payload.startswith("fb_") else None
Events without a uid still record and back-fill once the click is bound — but bind as early as you can.
3. Send events from Python
Here's a self-contained async client (only httpx):
import httpx, time
TRACKER = "https://xtracker.cc"
BEARER = "<BEARER>" # server-side only
async def send_event(event, *, uid=None, value=None, currency=None, eid=None):
payload = {"event": event, "occurred_at_ms": int(time.time() * 1000)}
if uid: payload["uid"] = uid
if value is not None: payload["value"] = str(value)
if currency: payload["currency"] = currency
if eid: payload["eid"] = eid
async with httpx.AsyncClient(timeout=5) as c:
r = await c.post(f"{TRACKER}/events", json=payload,
headers={"Authorization": f"Bearer {BEARER}"})
return r.json() # {"ok": true, "status": "queued" | "duplicate"}
async def bind(uid, tg_id):
async with httpx.AsyncClient(timeout=5) as c:
r = await c.post(f"{TRACKER}/bind", json={"uid": uid, "tg_id": tg_id},
headers={"Authorization": f"Bearer {BEARER}"})
return r.json()
And the funnel, in order:
await bind(uid, message.from_user.id) # once, after /start fb_<uid>
await send_event("subscribed", uid=uid)
await send_event("registration", uid=uid, eid=str(trader_id))
await send_event("deposit", uid=uid, value="25.00", currency="USD",
eid=f"dep-{transaction_id}")
4. Or fire a postback (no code)
For systems that can only hit a URL (ad networks, a partner's postback field), use the header-less GET /e endpoint with the ingest token:
https://xtracker.cc/e?token=<INGEST_TOKEN>&event=registration&uid={uid}&eid={txid}
For a deposit, add the amount:
https://xtracker.cc/e?token=<INGEST_TOKEN>&event=deposit&uid={uid}&value={amount}¤cy=USD&eid={txid}
The {...} parts are macros the upstream system substitutes with its own placeholders.
5. Parameter reference
This is the bit worth bookmarking.
| Param | Where | What it is · how to generate |
|---|---|---|
event |
required | Closed set: contact / subscribed / registration / deposit. Pick the one matching what the user did. |
uid |
optional | The click id from the deeplink start=fb_<uid>. You don't generate it. Events without it back-fill once bound. |
tg_id |
/bind |
Telegram user id — message.from_user.id. |
eid |
recommended | Your unique id for this exact conversion (e.g. the PO trade/transaction id). Makes re-sends idempotent — the same eid is recorded once. |
value |
deposit only | Numeric string, e.g. "25.00". From the payment/postback. |
currency |
optional | ISO-4217, defaults to USD. |
occurred_at_ms / t |
optional | Epoch milliseconds — int(time.time()*1000). Omit to let the server stamp receive-time. |
Bearer |
POST auth | Authorization: Bearer … for the write API. Rotate to generate. Server-side only. |
token |
GET auth | Ingest token in the query string for GET /e. Lower-trust — postbacks only. |
6. Idempotency — don't double-count deposits
A retried call must never count a deposit twice. Two mechanisms:
occurred_at_msis pinned per logical event; reusing the same value on a retry dedups.eiddedups independent of time — a postback that re-fires seconds later is ignored. Strongly recommended for deposits.
The response tells you what happened: "status": "queued" (new), "duplicate" (deduped — also success), or "invalid_event" / "invalid_value" / "invalid_time" for a bad field.
You're done
Send a test deposit, watch it appear in the live feed and revenue widget on your dashboard. Next: wire a Pocket Option postback.
Keep reading
How to find and fix the leak in your funnel
A practical walkthrough: use the funnel breakdown and conversion gauge to pinpoint exactly where users drop off, form a hypothesis, fix one thing, and measure the lift.
PlaybookKeep your finger on the pulse: real-time conversion monitoring
A live dashboard isn't a vanity metric — it's an early-warning system. Here's how watching conversions in real time catches broken funnels and dead campaigns before they burn your budget.
PlaybookSame budget, more deposits: optimizing creatives with per-link data
Clicks lie. Deposits don't. Use per-link attribution to see which creative actually drives revenue — then move spend to the winner and kill the losers.