Server-side events

Send named conversion events from your own backend, and Tiny Funnel attaches them to the visitor who triggered them.

Server-side events

Some conversions never happen in the browser. A Shopify app install finishes in an OAuth callback, a subscription starts in a webhook handler, an invoice is paid on a schedule. The server-events API lets your backend send those moments to Tiny Funnel as named events, and each one is attached to the visitor who caused it.

A server event can never be a funnel's first sight of someone. It attaches to a visitor your funnel has already seen through the script tag, or it is dropped. That is deliberate: an event with no browsing behind it has no entry page, no referrer and no journey, and would only muddy your numbers.

The endpoint

POST https://tiny-funnel.com/api/v1/events
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

Your API key is in the Install panel of your funnel, under the "Server side" tab. It is a secret: keep it in your backend's environment, never in page HTML or client-side code. Regenerating it in the Install panel invalidates the old key immediately.

The server-side tab of the Install panel, with the API key and recent deliveries

curl -X POST https://tiny-funnel.com/api/v1/events \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "app_installed",
    "ip": "203.0.113.9",
    "uid": "install-8271"
  }'

Fields

Field Required What it is
name yes The event name, 1 to 64 characters of letters, numbers, _, - and .. Case-sensitive, and it must match a Server event step's name exactly to count in the funnel.
external_id one of these Your own user id for the visitor, the same value your pages pass to tf.identify() or a tf-user meta tag. The strongest match: it follows the person, whichever browser or device they are in now. See the identity docs.
visitor_token one of these The visitor's Tiny Funnel id, read client-side from window.tf.visitor() and passed through your own stack. Exact, but tied to one browser.
ip one of these The original visitor's IP address, taken from the request your backend handled. Never your own server's address.
user_agent no The original visitor's browser user agent, from the same request you took the ip from. Used only to keep an IP match off the wrong device: a Windows conversion will never attach to an iPhone visitor. Send it whenever you send ip.
uid recommended Your own idempotency key for this event, up to 64 characters. Send the same uid on a retry and the event is only counted once.
occurred_at no ISO 8601 timestamp of when the event happened. Defaults to when it arrives.
metadata no A small JSON object of your own, shown with the event in the visitor's journey.

Send every matcher you have. They are tried strongest first: external_id, then visitor_token, then ip.

How matching works

  • By external_id: the event lands on the visitor your site identified with that id, whichever browser or device they are using now. Requires your pages to identify signed-in visitors — see the identity docs. And it works both ways: an event that matches by token or IP but carries an external_id writes that id onto the visitor, so your server events double as the most trustworthy identify there is.
  • By token: exact. The event lands on that visitor, in the one browser that minted the token.
  • By IP: the event lands on the funnel's most recently seen visitor from that address within the last 30 days, ignoring bots. IPv6 addresses match on their /64 prefix, since devices rotate the low half daily. An address names a network, not a person (a household or office can share one), so if you also send user_agent, candidates from a conflicting device family are skipped and a wrong-device match is refused outright: a missed match beats a wrong one. One warning from experience: if your marketing site and your app resolve differently (say one has IPv6 and the other does not), the same person can present a v6 address to one and a v4 address to the other, and those can never match. Give both properties the same connectivity, or carry a stronger matcher.
  • No match: the event is dropped, and recorded. The Install panel's server tab shows every unmatched event from the last 30 days, with which matchers it carried, so a misfiring integration is visible instead of silent.

Your visitor's raw IP is used once, in memory, to compute the same salted hash the script tag's hits are stored under. It is never written to disk, never logged and never stored.

IP matching is honest but not perfect. Shared office networks and mobile carriers can put many people behind one address, which is why the event attaches to the most recent visitor and why a stale address matches nothing. Where your stack can carry visitor_token end to end, prefer it.

Counting it in your funnel

Add a step of type Server event in the step editor and give it the event name, like app_installed. The step counts a visitor the moment an event with that exact name attaches to them. Per-step "when" conditions work like any other step. Events whose name matches no step still show up in the visitor's journey, which is a handy way to check an integration before wiring the step up.

Responses

Status Meaning
202 Accepted and queued. The body carries the uid used, yours or a generated one. Matching happens asynchronously, so acceptance does not mean a visitor matched.
401 Missing or invalid API key.
403 The funnel is not currently ingesting (paused, or billing has lapsed).
422 Missing or malformed name, or neither ip nor visitor_token given.
429 Rate limit reached. Retry after a minute.

Do it asynchronously

Send events from a background job or queue, never inline in a user-facing request. Your checkout should not wait on our API, and a network blip should not lose the event. The pattern that works:

  1. In the request handler, capture the visitor's IP (and visitor_token if you carry it) and enqueue a job with a stable uid.
  2. The job posts the event and treats any non-2xx as a logged failure, retried by your queue's own retry policy.
  3. Because the uid rides along, a retried delivery is never double-counted.

Getting the visitor token

On any page running the Tiny Funnel script tag, window.tf.visitor() returns the visitor's id once tracking has booted. Put it in a hidden form field, a checkout attribute or a header to your own backend, store it against the user or order, and send it with the server event later. It outlives the session, so an event days after the visit still matches exactly.

Better still, if your visitors sign in: identify them once with tf.identify() (or a tf-user meta tag) and send external_id with your server events instead. The token names a browser; your user id names the person. The identity docs cover both, plus how to keep one journey across your domains and through emailed links.

see your funnel in two minutes

One script tag, no cookie banner, no dashboard to assemble. $10 a month per funnel, and the first week is free without a card.

Start for free