You may hear these endpoints called “webhooks”, because they are often wired up as the destination of another system’s webhook. There is no callback, no signature header, and no subscription model — just two
POST endpoints authenticated with an API key.Before you start
Each API key is scoped to a single project. To send data to a different project, use a key issued for that project.
Endpoints
Base URL
The API is write-only. There are no endpoints to read events or user properties back, and no delete endpoints.
Confirm the exact base URL for your environment with your implementation contact before going live.
Authentication
Send your API key as a bearer token on every/events and /users call:
Request conventions
- Send
Content-Type: application/jsonon every request. - Bodies must be UTF-8 JSON. Unknown extra fields are ignored.
- Requests are abandoned after 30 seconds. Set your client timeout to 30 seconds or less.
- Every response includes an
X-Request-Idheader. Log it — it is the only handle support has for tracing a specific call. - Only
200means the request was accepted:
Send events
Event fields
At least one of
user_id or device_id must be non-empty. If you send only one, Klaritics currently fills the other with the same value. Send both when you have both.source_ip is mandatory
Events sent from your servers would otherwise be recorded with your server’s IP. Pass through the originating client IP — for example, the first address in X-Forwarded-For at your own ingress — so geographic enrichment is correct.
If an event has no end user, such as a batch job, you still need to send a parseable value. Agree a convention with your implementation contact rather than inventing one per caller.
Property values
A batch is accepted or rejected as a whole
If any event in the batch is invalid, the entire batch is rejected with400 and nothing is ingested. The response lists every failing field, indexed by position in your batch:
Set user properties
$user_id and $set, with the $ prefix.
How updates are applied
The update is a merge, not a replace:- Keys you send are created or overwritten.
- Keys you don’t send are left untouched.
- The profile is created on the first write.
Limits
That puts sustained event throughput at roughly 10,000 events/sec per project. If you need more, talk to your implementation contact before ramping up.
/users is not rate limited, but pace large profile backfills rather than sending them as fast as possible.
To stay under the limits, buffer events and send batches of up to 100, keep batches well under 1 MiB, and reuse HTTP connections.
Errors and retries
Branch on the HTTP status and on
validation_errors[].field. Treat the message strings as human-readable, not stable identifiers.
Recommended handling
Retry-After header is returned on 429. Back off exponentially — for example 250 ms, 500 ms, 1 s, 2 s, capped.
Retrying is safe
Duplicate events are removed later in the pipeline, so when in doubt, retry — a duplicate is recoverable, a gap is not. That includes retrying after a timeout or network error, when you can’t tell whether the request was accepted. Resending the same/users payload is naturally safe, since it merges the same values.
Include a stable business identifier such as order_id in properties anyway. It makes your own reconciliation much easier.
Accepted is not the same as queryable
A200 from /events means the batch was valid and accepted for ingestion. It reaches reports after normal pipeline lag, and there is no status endpoint to poll. A 200 from /users means the profile was saved.
time is not bounded, so backfills and late events are accepted. Always send the real event time, not the time of the API call, or backfilled data lands on today’s date.
Health check
200 {"message":"ok"} when healthy and 503 {"message":"health check failed"} otherwise. Use it as a reachability check, not as a replacement for your own monitoring.
Examples
Client with batching and retries
Python
Python
Node.js
Node.js
Go
Go
Gotchas
The /events body must be a bare JSON array
The /events body must be a bare JSON array
Send
[{...}], not {"events":[{...}]}. An object returns 400 invalid request body.Event names must be lowercase snake_case
Event names must be lowercase snake_case
Purchase Completed, purchaseCompleted, purchase-completed, 2fa_enabled, and _internal are all rejected. Use purchase_completed and two_fa_enabled. Events can’t be renamed afterwards, so settle the naming up front.time is milliseconds, not seconds
time is milliseconds, not seconds
A seconds-based timestamp passes validation because it’s greater than
0, then silently lands in 1970. Multiply by 1000.Large integers lose precision
Large integers lose precision
Numbers are parsed as JSON doubles, so integers beyond 2^53 aren’t exact. Send large IDs — Snowflake IDs, 19-digit order numbers — as strings.
Avoid . and $ in property keys
Avoid . and $ in property keys
A
. in a key is treated as a path separator, and $ is reserved. Stick to [a-z0-9_] keys.properties cannot be empty
properties cannot be empty
"properties": {}, or no properties field at all, is rejected. If an event genuinely has no attributes, send something meaningful such as {"source": "billing_service"}.Wiring a third-party webhook to this API
Wiring a third-party webhook to this API
This API doesn’t call back or verify signatures. Put a small relay in between that authenticates with your API key and reshapes the third party’s payload into the schemas above.
Go-live checklist
- Project ID and API key stored in a secret manager, never in source or client-side code
- Base URL confirmed for your environment
Authorization: Bearer <key>andContent-Type: application/jsonon every request- Events batched — at most 100 per request, body under 1 MiB
- Event names matching
^[a-z][a-z0-9_]*$, reviewed as a tracking plan timesent as UTC epoch milliseconds, from the real event timeuser_idand/ordevice_idon every event, consistent with your SDK-side IDssource_ipset to the end user’s IP, not your server’s- Large IDs sent as strings; string values at most 1024 bytes
- Retries with exponential backoff and jitter on
429,5xx, timeouts, and network errors — no retries on400or401 X-Request-Idlogged for every response- User properties written before or alongside the events they should enrich
- Client timeout of 30 seconds or less, with connection reuse
Next steps
Client SDKs
Track events from web and mobile apps.
Event naming
Naming conventions for events and properties.