Docs·Tracking
/1.0/track/eventTrack an event
Records a custom event: something a visitor did that is not a pageview, such as finishing a signup or starting a checkout. Events show on the Events page, can be goals and funnel steps, and carry properties you can filter and break down by.
Where the tracking script runs, send events with Spreelytics.track(), or tag elements with
data-spree-track and let the script send the clicks (see Track events). Call this
route yourself where the script cannot run, or from your server when it is the first to know that
something happened, such as a signup confirmed by email. For payments, use
Record a payment instead: events from this route never carry revenue.
The request
POST https://sdk.spreelytics.com/1.0/track/event, in either encoding:
- Form, as the script sends it:
Content-Type: application/x-www-form-urlencoded, with aclientKeyfield holding your client key and adatafield holding the JSON object below as a string. - JSON:
Content-Type: application/json, the JSON object below as the body, and the key in the address:?clientKey=YOUR_CLIENT_KEY. LeaveclientKeyout of the JSON body. This route does not accept the field and answers400.
Your client key is in Settings → API keys, under Site tracking key. Getting started covers both encodings in more detail.
Fields
| Field | Type | Required | What it is |
|---|---|---|---|
visitorId | string | Yes | The id of the browser, the same one its pageviews carry. On a page that runs the script, Spreelytics.getVisitorId() returns it. |
sessionId | string | Yes | The visit the event happened in, the same id its pageviews carry. On a page that runs the script, Spreelytics.getSessionId() returns it. |
eventName | string | Yes | The event's name, such as signup_completed. Names are case sensitive: signup_completed and Signup Completed are two different events. |
timestamp | string | Yes | When it happened, as an ISO 8601 date and time in UTC, such as 2026-10-01T09:31:00.000Z. It is stored as sent. |
event_data | object | No | The event's properties, such as { "plan": "growth", "seats": 5 }. Any JSON object; properties set to null are removed. |
attributes | object | No | Facts to save on the person, such as { "company": "Acme" }. Applied only if the visitor has already been identified, and ignored otherwise. The keys work as on Identify a visitor. |
type | string | No | custom_event, link_click or scroll. Leave it out for your own events: it defaults to custom_event. The script uses link_click for tracked link clicks, and scroll for the scroll depth it sends when a visitor leaves a page. |
contactId | string | No | Accepted and ignored. Who a visitor is comes from Identify a visitor. |
Any other field makes the request fail with 400.
Properties or person attributes
The two objects do different jobs:
event_datadescribes this one occurrence. Each event keeps its own copy, and it is what goal conditions, filters and breakdowns on event properties read.attributesdescribes the person and stays on their profile. A new value replaces the old one, so a value that changes every time, such as an order id, belongs inevent_data.
Nested objects in event_data are allowed. Track events shows how properties appear
in reports.
A property named revenue, or any other amount, is stored like any other property and adds nothing
to your revenue numbers. Payments come in through Record a payment or a
connected payment provider (see Revenue).
How events fit into visits
- The event joins the visit named by
sessionIdand takes on that visit's source, channel and UTM values, so you can break events down by where the visit came from. - If Spreelytics holds no pageview for that session, because none was sent or the last call for it was more than 30 minutes ago, the event is still stored and counted, but without a source or channel, and the visit's own counts do not change.
- A visit with an event in it is not a bounce. When the script tracks the visit, though, it decides bounce from the events it sent itself, so an event you add from a server does not change that.
- Custom events count towards your plan's monthly event allowance (see Billing). Link clicks and scroll events do not.
What it answers
| Answer | What it means |
|---|---|
200 with an empty body | Accepted. The event appears in the app shortly after. |
200 with the body OK | Dropped and not stored. See below. |
400 | A required field is missing or has the wrong type, or the body has a field this route does not accept, clientKey included. The JSON answer names each problem under fields. |
401 with the body Event API: Unauthorised | No client key, an unknown client key, or a request from a site that is not in Allowed domains (Settings → Details). |
413 | The body is larger than 250,000 bytes. |
500 | In the form encoding, data is not valid JSON. |
An event is dropped, and still answered with 200 and OK, when:
- its User-Agent belongs to a bot, a crawler, a command line tool or an HTTP library (curl, Python's requests, axios, node-fetch and others), or names a headless browser or test tool;
- it comes from an address in the published ranges of Amazon Web Services, Google Cloud, Oracle Cloud, DigitalOcean or Linode;
- your workspace has recorded three times its monthly event allowance (one and a half times above 1 million events).
The full rules are in What the Tracking API drops.
Calling it from a server
The usual pattern: the browser hands its ids to your server, and the server sends the event once it
knows the thing really happened. In a page that runs the script, add Spreelytics.getVisitorId() and
Spreelytics.getSessionId() to the form or request your server receives, then send the event with
them.
- Check where your server runs. A request from Amazon Web Services, Google Cloud, Oracle Cloud, DigitalOcean or Linode is dropped, whatever it contains. Vercel and other hosts built on those clouds are included.
- Send a browser's User-Agent. Your HTTP library's own User-Agent is dropped as a bot. This route does not read location, browser or device from the request, so the User-Agent only has to pass that check. Forwarding the visitor's own is the simplest way to do it.
- Send an
Originheader with a hostname from Allowed domains, such ashttps://www.example.com, or the call is refused with401.
On a cookieless audience
With cookieless tracking on, the collector ignores the visitorId you send and works
out the visitor from the request's address and User-Agent. It also puts the event in the visit it
holds for that visitor; when it holds none, the event keeps your sessionId and lands outside any
visit. From a server, every call shares the server's address, so the event is pinned on the wrong
visitor. Send events from a server only to audiences that use the cookie.
Request
# curl's own User-Agent is dropped as a tool, so this sends a browser's.
curl -X POST 'https://sdk.spreelytics.com/1.0/track/event?clientKey=YOUR_CLIENT_KEY' \
-H 'Content-Type: application/json' \
-H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36' \
-H 'Origin: https://www.example.com' \
-d '{
"visitorId": "vid_6f1c2a4e-8d3b-4c55-9a7e-2b8f0c9d1e23",
"sessionId": "sess_0b7d9e4a-3c21-4f6a-8e5d-7a9c1b2d3e4f",
"eventName": "signup_completed",
"event_data": { "plan": "growth", "seats": 5 },
"timestamp": "2026-10-01T09:31:00.000Z"
}'
// In a browser, without the tracking script. These are the ids saved by
// the browser sample on Track a pageview.
const ids = JSON.parse(localStorage.getItem('my_ids'));
await fetch('https://sdk.spreelytics.com/1.0/track/event', {
method: 'POST',
body: new URLSearchParams({
clientKey: 'YOUR_CLIENT_KEY',
data: JSON.stringify({
visitorId: ids.visitorId,
sessionId: ids.sessionId,
eventName: 'signup_completed',
event_data: { plan: 'growth', seats: 5 },
timestamp: new Date().toISOString(),
}),
}),
});
# On your server, once the signup is confirmed. visitor_id and session_id came
# from the browser (Spreelytics.getVisitorId() and Spreelytics.getSessionId()).
from datetime import datetime, timezone
import requests
requests.post(
"https://sdk.spreelytics.com/1.0/track/event",
params={"clientKey": "YOUR_CLIENT_KEY"},
headers={
# The visitor's own User-Agent. The requests default is dropped as a tool.
"User-Agent": visitor_user_agent,
# A hostname from Allowed domains.
"Origin": "https://www.example.com",
},
json={
"visitorId": visitor_id,
"sessionId": session_id,
"eventName": "signup_completed",
"event_data": {"plan": "growth", "seats": 5},
"timestamp": datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z"),
},
)
Response
200 OK
(empty body)
Related
- Track events: sending events from the page with the script.
- Goals: count the people who sent an event, or only some of its occurrences.
- Identify a visitor: tie the visitor to a person, so
attributesapply.