Events
POST /api/events records an event against a contact from your own systems — a
purchase confirmed by billing, a support ticket opened, a CRM stage change. It is
the server-side counterpart to the JavaScript tracking snippet, for the things a
browser cannot see.
Events feed contact scoring, segment conditions, and sequence entry triggers, so this is how an external system makes something it knows about act on sending.
Reading events back is not part of the API. A contact’s activity, and the account-wide feed, are read in the app; for bulk contact data, use a contact export.
Required scope#
| Endpoint | Scope |
|---|---|
POST /api/events | events:write |
Recording an event#
POST /t/{tenant_slug}/api/events
X-API-Key: sonar_xxxxx
Content-Type: application/json
{
"contact_email": "jamie@example.com",
"event_name": "subscription_renewed",
"type": "renewal",
"properties": {
"subscription_id": "sub_987",
"product_name": "Pro Plan",
"value": 99.00,
"currency": "USD"
},
"occurred_at": "2026-04-14T18:42:11Z"
}
Identify the contact with contact_id or contact_email — one is required.
An unknown address is 404 CONTACT_NOT_FOUND by default; enable
auto_create_on_event in workspace settings to have a minimal contact created
from the email instead, which is what a one-call billing integration usually
wants.
Returns 202 with a warnings array. Validation is deliberately lenient: a
property that does not match the activity type’s recommended shape is warned
about, not rejected, so a schema drift on your side never drops the event.
See event-tracking.md for the full field reference, the activity-type list, and per-type examples.
Errors#
| Code | Status | Meaning |
|---|---|---|
INVALID_INPUT | 400 | event_name is missing, or neither contact_id nor contact_email was given. |
CONTACT_NOT_FOUND | 404 | The contact did not match, and auto_create_on_event is off. |
INSUFFICIENT_SCOPE | 403 | The key is missing events:write. |
See errors.md for the full envelope.