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:
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 sendsuserId 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
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.
Link identities explicitly
When the caller has no cookie to read — a webhook, a cron job — state the link directly: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
SetinsertId 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.