Event Tracking API: Fire Custom Goals From Web and Mobile in One Call
Key Takeaways
- •An event tracking API lets you fire custom goals (signup, trial started, purchase, upgrade) with a single HTTP call, using the same request shape from a browser, a mobile app, and your own server.
- •Send events server-side wherever you can: a request from your backend cannot be blocked by an ad blocker, a tracking-prevention setting, or a user who never loads the page, so your counts stay complete.
- •Every event needs a stable visitor or user identifier so actions taken on web and in your app collapse into one person instead of two, and a client-generated event ID so a retried request never double-counts.
- •Keep metadata small and typed: send value in a known currency, an order or plan ID, and the traffic source or ad click ID, not a giant free-form blob you will regret querying later.
- •The events that pay you are the ones that touch money, so the modern best practice is first-party, revenue-attached tracking: stamp the source onto the real Stripe charge at settlement so ROAS and LTV survive cookie loss, iOS privacy, and data retention.
An event tracking API lets you fire custom goals like signup, trial started, or purchase with a single HTTP call, and use the exact same request shape from a web page, a mobile app, or your own backend. Instead of stitching together a browser pixel here and a mobile SDK there, you describe each action once (an event name, who did it, and a little typed metadata) and send it to one endpoint. This guide covers the request shape, a reference table of the events worth tracking, client versus server tradeoffs, deduplication, and the one thing most setups get wrong: connecting those events to money you actually kept.
What is an event tracking API?
An event tracking API is a single endpoint you POST a small structured message to whenever something meaningful happens in your product. A page-view pixel tells you a URL loaded. An event tells you what the person did: added an item, started a trial, upgraded a plan, paid an invoice. You name the action, attach a few fields, and the API stores it against a visitor or user.
The reason to use an API rather than a client-only script is reach. A front-end pixel can only see what happens in the browser tab while it is open. An API accepts the same event from anywhere you run code: browser JavaScript for on-page actions, a mobile app for in-app actions, and your backend for anything the server knows first (a payment settling, a subscription renewing, a webhook arriving hours after the user left).
That last category is the whole point. A renewal, a delayed charge capture, or a refund never touches the user's browser, so a pixel physically cannot record it. An event API can, because your server calls it directly.
The request shape in plain English
Every event you send is a small JSON object with four parts: a name, an identity, a timestamp, and metadata. Here is what each field does, described in prose so you can map it onto whatever API you use.
- event is the action name. Keep it a short, stable, lowercase string like signup, trial_started, or purchase. Pick a naming convention on day one (snake_case verbs) and never rename an event in flight, because renaming splits your history into two lines that no longer add up.
- visitor_id is a stable, first-party identifier you generate the first time someone shows up and then reuse. This is what lets a click today and a purchase next week collapse into one journey. Store it in first-party storage on web and in the keychain or shared preferences on mobile.
- user_id is your own account ID, sent as soon as the person logs in or signs up. Sending both visitor_id and user_id is what merges the anonymous browsing that happened before signup with everything the known user does afterward.
- timestamp is when the action actually happened, in ISO 8601 with a timezone (or a Unix epoch). Always send it explicitly. If you let the server stamp arrival time, a queued or retried event lands minutes late and your funnel timing goes soft.
- event_id is a unique ID your client generates for this specific event (a UUID is fine). It is the single most important field for correctness, because it is how the API knows a retried request is the same event and not a second one. More on this below.
- metadata is a small map of typed extra fields: value, currency, an order or plan ID, the traffic source. Keep it small and predictable. A tidy set of five known keys is queryable; a giant free-form blob is a future migration you will resent.
So a purchase event, in words, is: event set to purchase, a visitor_id and user_id, a real timestamp, a fresh event_id, and metadata carrying the amount, the currency, the order ID, and the source that brought the buyer in. You send that same object whether the purchase happened in a browser checkout or inside your iOS app. One shape, every surface.
Which events should you actually track?
Track the handful of events that map to your funnel, not everything a user could conceivably do. A good default is one event per meaningful step from first touch to paid, plus the money events after. The table below lists the common ones, when to fire each, and the metadata worth attaching.
| Event | When to fire | Example metadata |
|---|---|---|
| page_view | A page or app screen loads | path, referrer, source, ad click ID |
| sign_up | Account is created | method (email, google), plan_intent, source |
| lead | Email captured or form submitted | form_id, list, value |
| trial_started | A free trial begins | plan, trial_days, source |
| add_to_cart | Item added to cart | product_id, price, currency, quantity |
| checkout_started | Checkout or paywall opens | cart_value, currency, item_count |
| purchase | A payment succeeds | order_id, value, currency, source |
| subscribe | A recurring plan starts | plan, interval, mrr, currency |
| upgrade | Plan tier increases | from_plan, to_plan, delta_mrr |
| refund | A charge is reversed | order_id, refund_amount, reason |
Two rules keep this table useful. First, carry the traffic source (and any ad click ID) all the way from page_view through purchase, so the sale can be credited to the channel that earned it. Second, always send value and currency together on money events; a number with no currency is a bug waiting to happen the day you sell in a second market. For a deeper treatment of naming and capturing sources, the UTM tracking guide covers the parameter conventions that feed these fields.
Client-side or server-side? Send it from the server when you can
Send events from your server whenever the data already lives there, and reserve the client for things that only happen in the browser or app. Server-side events are simply more reliable, for four concrete reasons:
- Ad blockers and tracking prevention do not touch them. A meaningful share of visitors run blockers, and Safari and Firefox actively strip third-party tracking. A request from your backend is invisible to all of that.
- They do not depend on the tab staying open. If a user closes the page the instant checkout completes, a client-fired purchase event may never leave the browser. Your server already knows the sale happened and can send it regardless.
- They cannot be spoofed. Anything you fire from a public page can be replayed or faked by anyone reading your source. Server events are authenticated with a secret key.
- They see the invisible steps. Renewals, delayed captures, dunning recoveries, and refunds happen far from any browser. Only your server can report them.
The practical model is hybrid: fire genuinely interactive events (a button click, time on a screen) from the client, and fire everything that involves money or account state from the server. The reason this is safe rather than double-counting is the shared event ID, which is the next section.
If most of your traffic comes through WordPress, the same client-versus-server logic applies to plugins and tag managers; the walkthrough on how to add analytics to WordPress shows where the tag lives and why a server hook is sturdier than a theme snippet. And if you want the full rationale for moving attribution off the browser entirely, cookieless tracking explained makes the case that first-party, server-side capture is now the default, not the fallback.
Idempotency: one event ID, no double counts
The rule that makes hybrid and retried tracking safe is idempotency: generate one event_id per real action and send that same ID on every copy of the event. When two requests arrive with the same event_id, the API keeps the first and discards the rest.
You need this in two situations that come up constantly:
- Retries. Networks fail. Your client or server will retry a failed POST, and without a stable ID the retry becomes a second purchase in your data. With an event_id, the retry is recognized and dropped.
- Web plus server for the same sale. When you fire a purchase from the browser for speed and again from your backend for reliability, both should carry the same event_id (generate it on the client, pass it to the server in the checkout request). The API dedupes the pair into one conversion.
A good default is a client-generated UUID created at the moment the action happens, cached with the pending request, and reused for the lifetime of that action including all retries. Treat the event_id as sacred: never regenerate it inside a retry loop, or you defeat the entire mechanism.
Merging web and mobile into one person
Fire the same event shape from both surfaces and tie them together with a shared identity, and a single user browsing on the web and buying in your app becomes one journey instead of two half-people. Two identifiers do the merging:
- visitor_id links anonymous activity. Generate it once per device and persist it (first-party storage on web, keychain or secure storage on mobile). If you can pass the web visitor_id into the app at install or login, you can even bridge the same human across devices.
- user_id links known activity. The instant someone authenticates, start sending your account ID on every event. The API then stitches the pre-login anonymous trail to the logged-in user.
The failure mode to avoid is firing web events with one ID scheme and mobile events with another, then wondering why your app users look like they never visited the site. One identity model, sent from every surface, or your funnel silently splits in two. Conversion funnel tracking goes deeper on keeping steps aligned across surfaces so drop-off is measured against the right denominator.
Batching, ordering, and rate limits
A few operational details keep a real integration healthy at volume.
- Batch high-frequency events. If you emit many events per second, send them in arrays rather than one request each. Keep interactive money events (a purchase) as their own immediate call so they are never stuck behind a queue.
- Do not depend on arrival order. Events can land out of order after retries or batching, which is exactly why you send an explicit timestamp on every event: order by when the action happened, not when the request arrived.
- Handle 429s with backoff. When you hit a rate limit, retry with exponential backoff and jitter, reusing the same event_id so the eventual success is not counted twice.
- Queue on the client, flush on your schedule. Mobile apps should buffer events locally and flush when the network is available, so an offline user still reports everything once they reconnect.
The step most setups skip: attach events to real revenue
Firing a purchase event proves an action happened. It does not prove which visit earned money you kept, and that gap is where most analytics quietly lies to you. A standard pipeline records the conversion at click time and credits whatever source it can still see in the browser right then. But cookie loss and iOS privacy erase the original source long before a subscription charge settles, so the sale files under direct or gets attributed to the wrong channel.
The modern best practice is first-party, revenue-attached tracking. In plain terms:
- Capture the traffic source and any ad click ID on your own domain the moment a visitor arrives, and carry it as the source field through every event.
- When the charge actually settles in Stripe, stamp that captured source onto the real order at settlement, not onto a guess made at click time.
- Because the attribution now rides with the order instead of a browser cookie, refunds and renewals adjust the true figure automatically. A refund is not just a refund event; it corrects the revenue credited to that channel.
This is the difference between counting purchase events and knowing your return on ad spend. When the source is welded to the settled charge, your ROAS and customer LTV survive cookie loss, iOS privacy, and data-retention windows, because they were never dependent on a cookie surviving until payday. Affiliateo is built around exactly this approach: it joins your first-party traffic to real Stripe revenue and stamps the ad source at the moment of sale, so the numbers hold up months later when the renewal or the chargeback lands.
For the mechanics of tying settled charges back to channels, attribute Stripe revenue to marketing channels walks through the join itself, and first-party ad attribution explains why capturing the source on your own domain is what makes any of it survive privacy changes.
A practical rollout, in order
You do not need to instrument everything on day one. Ship the money path first, then widen.
- Start with purchase and subscribe fired from your server, each carrying value, currency, an order ID, source, and a client-generated event_id. This alone gives you reliable revenue by channel.
- Add sign_up and trial_started next, so you can measure the top of the funnel against sales you already trust.
- Layer in the interactive events (add_to_cart, checkout_started) from the client, sharing event IDs with their server counterparts.
- Wire refund into your existing payment webhook so reversals correct the record instead of leaving inflated wins in your dashboard.
Track it this way and every event you fire has a clear job, a stable identity, an idempotency guard, and, for the events that matter, a direct line to revenue you actually kept.
Frequently asked questions
What is an event tracking API? It is an endpoint you send a small structured message to whenever something meaningful happens in your product (a signup, a purchase, a trial start), describing the action with a name, an identity, a timestamp, and a little metadata. Unlike a browser pixel, an API accepts that same event from web, mobile, or your backend, so actions that happen off the page (like a renewal settling later) still get recorded.
Should I track events on the client or the server? Send them server-side whenever the data lives on your server, and use the client only for things that genuinely happen in the browser or app. Server events are not blocked by ad blockers or tracking prevention, do not vanish when a tab closes, and cannot be spoofed. The shared event ID is what keeps a hybrid setup from double-counting.
How do I connect tracked events to real revenue? Capture the traffic source on your own domain when the visitor arrives, then stamp it onto the actual Stripe charge at settlement rather than guessing at click time. Because attribution rides with the order, refunds and renewals correct the true figure, and your ROAS and LTV survive cookie loss and iOS privacy.
Written by Jamal Brooks
Jamal is a product engineer at Affiliateo who writes about payments, integrations, and technical best practices.
