For servers, mobile apps, background jobs and anything else that cannot run the browser tracker. Every DataVisitors SDK speaks this protocol; you can speak it directly.
POST https://api.datavisitors.com/api/eventAuthorization: Bearer YOUR-INSTALL-KEYContent-Type: application/json
The key is an event-sending key for the project. It can write events there and read nothing back.
Give your servers their own sending key rather than the browser's install key. A key locked to browser origins refuses a request that carries no Origin header, which is every request a server makes — see Allowed origins.
The envelope
One request carries one project's batch.
{ "protocol": 1, "sdk": { "name": "your-app", "version": "1.0.0" }, "sent_at": "2026-09-19T10:00:00.000Z", "environment": "production", "events": [ { "event_id": "01J8ME9V7C3W5X2K9QZT4R6B8D", "event_name": "invoice_paid", "occurred_at": "2026-09-19T09:59:58.120Z", "anonymous_id": "sg_anon_9f2c1d7b4a", "session_id": "sg_sess_31c8a04e77", "context": { "page": { "path": "/billing", "referrer_host": "duckduckgo.com" }, "locale": "en-GB", "consent": "anonymous" }, "properties": { "plan": "growth" } } ]}
| Field | Required | What it must be |
|---|---|---|
protocol |
yes | 1. It bumps only on a breaking shape change. |
sdk |
yes | A name and a version, so a dashboard can tell your integration apart from the tracker's. |
events |
yes | At most 100 events, at most 512 KB for the whole batch. |
event_id |
yes | A ULID or UUIDv7 you mint. It is the deduplication key. |
event_name |
yes | [a-z][a-z0-9_]{0,63}. The sg_ prefix is the platform's. |
occurred_at |
yes | When it happened on your side. No further back than 7 days, no further forward than 10 minutes. |
anonymous_id, session_id |
with consent | ^[A-Za-z0-9_-]{8,64}$. Absent under stateless. |
user_id |
identified only | Only when context.consent is identified. |
context.consent |
yes | stateless, anonymous or identified. pending is refused on purpose. |
properties |
no | At most 32 keys, depth 2, string values up to 1 KB. |
environment |
no | [a-z][a-z0-9_-]{0,31}, default production. |
Unknown top-level fields are refused outright — a typo in an envelope field is a bug you want to hear about. Unknown property keys are your own schema and are kept.
The answer
HTTP/1.1 202 Accepted{ "accepted": 1, "rejected": [], "regime": "optout"}
- The
202is returned after the event is stored. An acknowledged event is a stored event. rejectedcarries one entry per refused event, each with itsindexin your batch and a stablecode. Nothing is ever dropped without a result — Error codes lists every code.regimeis the privacy regime the server resolved for this visitor. Underoptout, a batch sent with no identifiers is answered with theanonymous_idandsession_idthe server derived, so a browser can adopt them. A server-side sender can ignore all of this.
Statuses other than 202 refuse the whole request: 400 for a batch that cannot be read or fails a rule, 401 for a key that is unknown or revoked, 403 when the key's origin rules refuse this request, 405 for anything but POST, 429 when you are sending too fast, and 503 when storage is briefly unavailable — retry that one.
Sending safely
- Mint an
event_idper event and keep it across retries. A retried batch is de-duplicated by that id for 24 hours, or the project's last 100,000 events, whichever is shorter. A restart of the service forgets the window, so at-least-once is the floor we promise. - Retry
429and5xxonly. A400,401or403will be refused identically next time; fix the request instead. - A key that names its websites refuses every other one with
403. A leadingwww.is forgiven; any other subdomain is a different website and needs its own entry. Refused requests are counted under Tracking → Ignored traffic. - One identity per visitor, not per process. A server that mints a fresh
anonymous_idper request turns every request into a new visitor. Counting correctly has the whole failure and its fix. - Do not send page views from the server when the browser tracker is installed. They are the same page load counted twice, under two identities.
Most integrations should use a maintained SDK instead of speaking this by hand — see SDKs.