Skip to main content
For languages Trevo does not ship an SDK for — Go, Python, Ruby, Elixir, PHP, Java, Dart. Tracking is a plain HTTP POST with no client-side logic, so there is nothing to get subtly wrong.

Do not implement assignment yourself

Assignment is a hash whose exact behaviour is a frozen contract shared by every Trevo SDK: FNV-1a over UTF-16 code units, not UTF-8 bytes. An implementation written against bytes agrees with ours on ASCII identifiers and diverges on every accented name, every CJK id, every emoji. It passes your tests and silently corrupts that customer’s experiment months later. Nothing errors — the numbers just stop meaning anything. If you only need to record conversions, everything below is safe and sufficient: resolve variants in the browser or a Node service, and use REST purely for tracking. If you genuinely need assignment in an unsupported language, implement the bucketing spec and pass every case in the published conformance vectors before sending a single exposure.

Authentication

A server key (tsk_secret_…) from Settings → API keys, platform Server:
Publishable tsk_live_ keys are for browsers. A secret key sent with an Origin header is rejected, because that combination means it has been shipped into client code.

Send events

Up to 500 events per request — batch rather than sending one request per event. The body also accepts a top-level sentAt (ISO 8601), set when the batch leaves your client.

Send both ids

Trevo links an anonymous journey to a user the first time it sees an event carrying both. A backend that only ever sends userId never creates that link, and the user’s pre-login browsing is never attached to their conversion.

Timestamps and wrong clocks

Events are never dropped for their timestamp. receivedAt - sentAt gives your client’s clock offset, and every event in the batch is corrected by it. That distinguishes “this clock is wrong” from “this event is genuinely old”, which comparing against the server clock alone cannot do.
  • Offsets under 2 seconds are treated as network transit and ignored
  • Corrections are capped at ±24 hours
  • After correction, timestamps older than 7 days or more than 1 hour in the future are clamped to the boundary and marked, not discarded
Send sentAt if you buffer events. Without it, timestamps are taken at face value and only the sanity clamp applies — fine for a server sending events as they happen, wrong for anything that queues. When the caller has no cookie to read — a webhook, a cron job — state the link directly:
Returns 202 with an outcome of linked or already-linked. Sending the same pair repeatedly is a no-op, so it is safe inside a handler your provider may replay. An anonymousId belongs to one user. Pointing one at a different user returns 409 and changes nothing — that request is usually a shared browser or a reused cookie, and honouring it would credit one person’s browsing to another.

Idempotency

Set insertId to a stable value derived from the thing that happened — an order id, a subscription id, ${userId}:${invoiceId} — and Trevo records that event once no matter how many times you send it. This matters most where you least want a duplicate: payment providers replay webhooks, and a subscription_started counted twice inflates the conversion rate of whichever variant that user was in. Without insertId, retries are not deduplicated. A request that timed out after the server accepted it will be counted twice if you send it again. One boundary: deduplication is scoped to the calendar month the event was recorded in. Two deliveries of the same insertId landing in different months are both kept. Rarely reachable in practice — but if you replay historical events in bulk, do it within a month’s window.

Responses and retries

A timeout or dropped connection is the ambiguous case: the request may or may not have landed. Send insertId and retry freely.

Minimal example