Skip to content
Data Delivery

Your data, where you make the decision

Every identification can reach your systems two ways: pushed to your server the moment it happens, or pulled by you at the exact second you decide. Both channels carry the same numbers — that part is locked by a test, not by a promise.

Two channels

Push or pull

Webhooks push events to you as they happen. The Data API lets you ask, at the moment you need an answer. Most teams run both: webhooks to record and react, the Data API to check inline.

Webhooks — push, in real time

We POST a signed JSON event to your endpoint the moment something happens: a visitor is identified, an account takeover is flagged, a bot attack starts. Nothing to poll, nothing to schedule.

Best for: recording every visit, reacting to attacks, feeding your warehouse or SIEM.

p50 delivery latency 44–140 ms, event to your endpoint.

Data API — pull, on demand

A private server-to-server API. Your backend authenticates with a secret key and reads exactly what we know about a visitor at the second it decides — typically inside a login or checkout handler.

Best for: an inline check before you charge a card, approve a signup or unlock an account.

Available from the Pro plan.

Webhooks

Four event types, one envelope

Every delivery arrives in the same envelope, with the event type in the body and in the X-Tracio-Event-Type header — so a single handler can route all four.

Visitor identified

The core event: a visit was scored. Carries the visitor ID, browser and OS, geo and network, the bot verdict and the risk decision. Delivered in phases — a primary event at page load, then a late or correction phase when slower evidence changes the verdict. Correlate phases by requestId.

identification

Account takeover

The account-takeover detector fired on a visit: the device behind a known account no longer looks like the device that owns it. It arrives as its own event, with the account context attached, rather than hiding inside an identification body.

account_takeover

Bot attack

A surge of automated traffic on your workspace. This one has no visit behind it — it is a workspace-level alert, so the visit blocks are simply absent from the body instead of arriving as empty shells with zeroed scores.

attack_detected

Reputation change

A profile moved between reputation bands. Same envelope as the attack alert — a profile-level event with no visit attached, carrying the new band and the previous one.

reputation_changed

A delivery, trimmed

This is the base body. Pro adds visit velocity; Business adds verdict reason codes, behaviour signals, guidance and cross-browser device data to the very same shape — new blocks appear, existing paths never move.

JSON
{
"version": 2,
"event": "identification",
"eventId": "req_8f21c4:primary",
"requestId": "req_8f21c4",
"phase": "primary",
"visitorId": "3f9a1b2c4d5e6f70",
"timestamp": "2026-07-30T12:00:00Z",
"geo": { "country": "DE", "city": "Berlin", "timezone": "Europe/Berlin" },
"network": { "vpn": true, "proxy": false, "tor": false, "datacenter": false },
"bot": { "result": "human", "score": 12 },
"identification": { "confidence": 0.97, "incognito": false },
"decision": { "action": "suspicious", "riskScore": 65.9 }
}

Every request carries two signatures

X-Tracio-Signature is an HMAC-SHA256 over the signature timestamp joined to the raw request body, keyed with your webhook secret — it proves the sender knows the secret you both hold. X-Tracio-Signature-Ed25519 is the platform signature: you verify it with a public key fetched from a well-known endpoint, so there is nothing secret to store on your side. The timestamp is part of the signed content, which is what makes an old capture useless to replay.

Verify against the raw request bytes — re-serialized JSON changes the bytes and the signature will not match. Retries carry the same X-Tracio-Event-Id, so deduplicate on it.

Headers on every delivery

Text
X-Tracio-Signature: t=1753444800,v1=5257a869e7ecebed...
X-Tracio-Signature-Ed25519: t=1753444800,kid=k1,v1=0Zx0M0n8...
X-Tracio-Event-Type: identification
X-Tracio-Event-Id: req_8f21c4:primary
X-Tracio-Delivery-Attempt: 1
X-Tracio-Payload-Version: 2
Reliability

Built to not lose events

Delivery runs on a dedicated fleet, and the queue — not the memory of a process — is the source of truth. That is what makes at-least-once real: if a delivery node dies mid-flight, the event is still in the queue and another node picks it up.

events per second through a single webhook, up from roughly 50 before the July rebuild
44–140 msp50 delivery latency from event to your endpoint
delivery attempts on a widening ladder, spread across up to 8.7 hours
of 90,000 events delivered in a drill that killed a delivery node under load

Retries that fit real outages

5 s, 30 s, 2 min, 10 min, 30 min, 2 h, 6 h. The first retries land inside a minute, so a brief restart of your service costs you nothing. Each pause is picked at random between half the listed value and the full one, so retries don't return as a single volley after an outage.

Auto-disable that doesn't misfire

A webhook is switched off only when failures both reach the threshold and have been going on for at least 15 consecutive minutes — a burst of queued deliveries during a restart won't kill the integration. A 410 Gone disables immediately. The dashboard shows the reason, the response code and a re-enable button.

Secret rotation without a gap

After a rotation both secrets stay valid for 24 hours and the header carries both signatures, so a match on either one is enough. You update your configuration inside the window instead of racing a cutover; "Revoke now" cuts the window short when you need it gone.

A delivery log you can read

Every attempt — response code, duration, error text — is visible per webhook in the dashboard, next to a test action that sends a signed sample payload to your endpoint so you can confirm your verifier before going live.

Data API

Ask at the moment you decide

A private server-to-server API on api.tracio.ai. Your backend authenticates with a secret key and reads its own data. It deliberately sends no CORS headers: a secret key grants access to everything in your workspace and must never reach a browser. Available from the Pro plan.

MethodPathReturns
GET/v1/visitors/{visitorId}Visitor summary: first and last seen, visit count, unique IPs and countries, browsers and devices, risk history — plus their latest session.
GET/v1/visitors/{visitorId}/sessionsSession list with cursor pagination and filters by date range, bot result and minimum risk score.
GET/v1/visitors/{visitorId}/sessions/latestThe latest session as a single object, with no list envelope.
GET/v1/sessions/{requestId}One specific session. Pass visitorId alongside it and the lookup goes through the visitor index instead of your whole history.
GET/v1/visitors/{visitorId}/velocityActivity over a window — 1h, 24h or 7d: how many visits, from how many IPs, out of how many countries, under how many accounts.

Checking a visitor at checkout

The typical call: inside your payment handler, before you authorize the card. One request, one answer, and the meta block reports the window you actually got — if you ask for six months and your plan retains 30 days, it returns 30 days and says so.

Request

bash
# Inside your checkout handler, before you authorize the card
curl -s -H "Authorization: Bearer $TRACIO_SECRET_KEY" \
"https://api.tracio.ai/v1/visitors/3f9a1b2c/velocity?window=24h"

Response

JSON
{
"window": "24h",
"events": 128,
"uniqueIps": 4,
"uniqueCountries": 2,
"uniqueAccounts": 1,
"meta": { "plan": "business", "retentionDays": 30 }
}

The same numbers everywhere

A visit that scores 65.9 in your dashboard scores 65.9 in the Data API and 65.9 in the webhook body. Two independent renderings could drift apart — scales are the classic way, one channel handing you 0.93 where the other says 93 — so a parity test builds a single visit, renders it through both channels and compares the public fields on raw JSON. The agreement is enforced, not asserted.

Guidance — Business and up

Advice, not just numbers

Scores tell you what we saw. Guidance tells you what to do about it, for the four decisions that actually cost money — computed by versioned rules, with the reasoning attached.

Take the payment?

Weighs risk, fraud reputation and the bot verdict before you authorize a card.

Accept the signup?

Catches the throwaway account before it exists — multi-accounting and reputation weigh heaviest here.

Let them in?

Tightens automatically when the account-takeover detector has fired on the visit.

Count the conversion?

Separates a genuine referral from a self-referral or an incentivized bot.

A four-word vocabulary

allowNothing worth acting on.
challengeAsk for a second factor.
reviewHold it for a human.
denyRefuse outright.

Each scenario gets one of four answers, and with it the basis it was issued on — the deciding axes from a fixed vocabulary: bot, risk, fraud reputation, behavior, multi-accounting, account takeover, network, affiliate pattern. You always know which axis moved the advice, without ever seeing signal names, weights or thresholds.

One calculation, three channels

The same guidance block rides the webhook, answers in the Data API and renders on the visitor card in the dashboard — one rule set, one result, no reconciliation on your side. Read the advice for your scenario rather than overall: overall is simply the strictest of the four, a summary for dashboards and not a payment decision. The rule version ships in the payload, so a change of rules is something you notice, not something you infer from advice that shifted.

JSON
"guidance": {
"version": 1,
"overall": "review",
"payment": "review",
"registration": "challenge",
"login": "allow",
"affiliate": "allow",
"basis": ["risk", "fraud_reputation"]
}
Integration

Five SDKs on the front end, two channels on the back

The browser side ships as five SDKs — vanilla JavaScript, React, Vue 3, Angular and Svelte 5. There are no server-side SDKs, and that is deliberate: your backend integrates over plain HTTP through signed webhooks and the Data API. Signature verification is a dozen lines against a reference vector we publish, and there is nothing extra to keep upgrading in your server dependency tree.

Browser SDKs
JavaScriptReactVue 3AngularSvelte 5
FAQ

Frequently asked questions

Wire it up in an afternoon

Create a webhook in the dashboard, point it at your endpoint and hit Test. Verify the signature against our reference vector, and the hard part is behind you.