FullVision
Tracker Setup

Server-side events

Send product events and identify people from your backend

Two endpoints let your backend tell FullVision what happened inside your product: POST /v1/events records something a person did, and POST /v1/persons records who a person is.

Use them when the browser tracker is not enough — a signup that completes on your server, a plan upgrade, a job that finished, or simply putting a name and an email on the anonymous visitor the tracker has been following.

Both live on https://db.fullvision.io and both take a secret key (sk_) carrying the events:write scope. A publishable pk_ key is rejected.

These are server-side calls only. The key they carry reads and writes your workspace — never send either request from a browser.

POST /v1/events — something happened

curl -X POST https://db.fullvision.io/v1/events \
  -H "Authorization: Bearer sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "event_name": "plan_upgraded",
    "external_id": "user_12345",
    "visitor_id": "fv_abc123",
    "properties": { "from_plan": "free", "to_plan": "pro" }
  }'
FieldWhat it doesWhen you'd use it
event_nameNames the event. Required. Any name except one starting with $, which is reserved for events the tracker captures itself.Always. Pick names you will still recognise in a report a year from now.
external_id (alias user_id)Your own user id for the person.Whenever the person is signed in. This is the identifier that stitches a browser visitor to a named person.
emailThe person's email.When you have an email but no stable user id.
visitor_id (alias fv_visitor_id)The fv_visitor_id cookie value, read off the incoming request.Send it whenever you have it — it is what links this event to the traffic that produced the visit.
propertiesFree-form detail about the event.To record what made this occurrence different: which plan, which amount, which feature.
traitsFree-form attributes of the person — see Person Traits.To update the profile at the same time as recording the event.

Send one event as a bare object, or several at once as { "events": [ … ] }.

properties describes the event; traits describes the person. Attributes put in properties are not merged onto the person record.

POST /v1/persons — who someone is

Records or updates a person without recording an event.

curl -X POST https://db.fullvision.io/v1/persons \
  -H "Authorization: Bearer sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "user_12345",
    "email": "jane@acme.com",
    "name": "Jane Doe",
    "traits": { "plan": "pro", "seats": 5 }
  }'

Use this one for a pure profile update — a plan change synced from your billing system, a name filled in later. Use POST /v1/events instead when a real event is happening and you want to set traits in the same call.

Responds 200 with { "person_id": "…" }. At least one of external_id, email or visitor_id is required; without one the call returns 400.

Which identifier wins

Both endpoints resolve the person the same way, in this order:

PriorityFieldWhat happens
1external_id (alias user_id)Finds or creates the person by your user id.
2emailFinds or creates the person by email. Used only when external_id is absent.
3visitor_id (alias fv_visitor_id)Attaches to the person that visitor is already linked to. An unlinked visitor resolves to nobody. Used only when the two above are absent.

Send more than one and the highest priority wins. Sending a visitor_id alongside a resolvable external_id or email also stitches that browser visitor to the person — which is exactly how an anonymous visit becomes an attributed customer, so send it whenever you have it.

How this feeds the reports

Once a visitor is stitched to a person, everything that person later does is attributable to the traffic that first brought them in. Product events show up as the events block on the reports, counted as people acquired via that channel, page, keyword or campaign; the people themselves are readable through People and Journeys.

Rate limit

Both endpoints share one per-workspace limit. On 429, wait the number of seconds in the Retry-After header and retry — see rate_limited.

On this page