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" }
}'| Field | What it does | When you'd use it |
|---|---|---|
event_name | Names 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. |
email | The 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. |
properties | Free-form detail about the event. | To record what made this occurrence different: which plan, which amount, which feature. |
traits | Free-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:
| Priority | Field | What happens |
|---|---|---|
| 1 | external_id (alias user_id) | Finds or creates the person by your user id. |
| 2 | email | Finds or creates the person by email. Used only when external_id is absent. |
| 3 | visitor_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.
