Events
Send workspace and custom events, and read the event stream behind webhooks: deliveries, bounces, opens and more.
Send an event
Records one workspace event (w:<name>) or custom event (c:<name>). Works with API keys that have the full or the events scope. See Event tracking for when to use which. Events are included with Pro, Business and Custom plans, with up to 100 event types on Pro and 1,000 on Business.
- A workspace event must be defined first. Its
propertiesare checked against the definition. - A custom event needs no definition. Its
propertiescan be any JSON object.
System events such as email.delivered are recorded by Emailit and can’t be sent. To record many events in one call, use Send events in a batch.
/eventsBody parameters
typestringrequiredw:<name> for a workspace event or c:<name> for a custom event, for example c:order.completed. The name has 1 to 100 letters, digits, ., - or _, and starts with a letter or digit.propertiesobjectThe event’s data. Default {}.
- Workspace events: only the properties of the definition, each with its type. Required properties must be present,
nullleaves an optional one empty, and dates are stored as ISO 8601 in UTC. See Property types. - Custom events: any JSON object, up to 32 KB and 8 levels deep.
emailstringcontact_idstringcon_…). Takes precedence over email. An ID that doesn’t match a contact in the workspace returns 422.anonymous_idstringoccurred_atstring | integercontextobjectpage (url, path, title, referrer, search), campaign (source, medium, name, term, content), locale and timezone. Unknown keys are dropped and long values are shortened.Contacts are only looked up, never created. Emailit doesn’t store IP addresses with events.
Returns
Returns 201 Created with the event: object, id, type, created_at and data. data.object holds what was stored:
objectstringworkspace_event or custom_event.namestringpropertiesobjectcontactobject | nullid and email of the contact the event belongs to. id is null when only the email is known. null when you sent neither.anonymous_idstring | nullanonymous_id you sent.occurred_atstringsourcestringapi for this endpoint. Events from the tracking script have js, and test events sent from the dashboard have dashboard.contextobjectAn API key with the events scope can’t read data, so it gets the event back without data, which holds the contact matched by email.
| Status | When |
|---|---|
400 |
The body isn’t a JSON object. |
403 |
The API key’s scope is neither full nor events, or the plan doesn’t include workspace and custom events: on Pay as you go, error is plan_required and required_plan is pro. Nothing is stored. |
422 |
The event is invalid. errors is keyed by field, such as type or properties.total, and message repeats the first problem. Also returned for a w: event that isn’t defined, for a contact_id that doesn’t match a contact, and for a type the workspace hasn’t sent before once it uses its plan’s number of event types (100 on Pro, 1,000 on Business). |
429 |
Too many requests in a minute, or the workspace reached its daily fair use limit for workspace and custom events (code: events_daily_limit). Nothing is stored. Try again after the number of seconds in the retry-after header. |
{
"object": "event",
"id": "evt_2yJm3Rk8VbN1sQ5tLwX7eZhD4cF",
"type": "c:order.completed",
"created_at": "2026-10-19T09:30:02.114000+00:00",
"data": {
"object": {
"object": "custom_event",
"name": "order.completed",
"properties": { "order_id": "1001", "total": 59.9, "currency": "EUR" },
"contact": { "id": "con_2wPq7Lm3XcN8bV1zKd4RtY6sHf9", "email": "jane@example.com" },
"anonymous_id": null,
"occurred_at": "2026-10-19T09:30:00.000Z",
"source": "api"
}
}
}{
"object": "event",
"id": "evt_2yJm3Rk8VbN1sQ5tLwX7eZhD4cF",
"type": "c:order.completed",
"created_at": "2026-10-19T09:30:02.114000+00:00"
}{
"error": "plan_required",
"required_plan": "pro",
"message": "Workspace and custom events are included with Pro and Business."
}{
"error": "properties.segment must be a string",
"message": "properties.segment must be a string",
"errors": {
"properties.segment": ["must be a string"]
}
}{
"error": "type w:customer.tier is not defined in this workspace. Create it under /v2/workspace/events first, or send it as a custom event (c:customer.tier).",
"message": "type w:customer.tier is not defined in this workspace. Create it under /v2/workspace/events first, or send it as a custom event (c:customer.tier).",
"errors": {
"type": ["w:customer.tier is not defined in this workspace. Create it under /v2/workspace/events first, or send it as a custom event (c:customer.tier)."]
}
}Send events in a batch
Records up to 100 workspace and custom events in one request, for example when your backend syncs orders on a schedule. Works with API keys that have the full or the events scope, on Pro, Business and Custom plans.
The batch is all or nothing. If any event is invalid, none is stored, and the response lists every problem by the event’s position in the list.
/events/batchBody parameters
eventsobject[]requiredtype, properties, email, contact_id, anonymous_id, occurred_at and context.Returns
Returns 201 Created with the stored events in data, in the order you sent them. Each item has object (event), id, type and created_at. Use Retrieve an event to read one with its data.
| Status | When |
|---|---|
400 |
events is missing, empty or has more than 100 items. |
403 |
The API key’s scope is neither full nor events, or the plan doesn’t include workspace and custom events: on Pay as you go, error is plan_required and required_plan is pro. Nothing is stored. |
422 |
At least one event is invalid. Nothing is stored. errors is keyed by events.<index>.<field>, for example events.3.properties.total, counting from 0, and message repeats the first problem. An event of a type the workspace hasn’t sent before is invalid once the workspace uses its plan’s number of event types (100 on Pro, 1,000 on Business). |
429 |
Too many requests in a minute, or the batch would take the workspace over its daily fair use limit for workspace and custom events (code: events_daily_limit). Nothing is stored. Try again after the number of seconds in the retry-after header. |
{
"data": [
{ "object": "event", "id": "evt_2yJm3Rk8VbN1sQ5tLwX7eZhD4cF", "type": "c:order.completed", "created_at": "2026-10-19T09:30:02.114000+00:00" },
{ "object": "event", "id": "evt_2yJm3Sa1XcP4uW8rMyB6fGjK0nT", "type": "c:order.completed", "created_at": "2026-10-19T09:30:02.114000+00:00" },
{ "object": "event", "id": "evt_2yJm3Td5ZeQ7vX2sNzC9hHkL3pU", "type": "w:customer.type", "created_at": "2026-10-19T09:30:02.114000+00:00" }
]
}{
"error": "events.1.email must be a valid email address",
"message": "events.1.email must be a valid email address",
"errors": {
"events.1.email": ["must be a valid email address"],
"events.2.properties.segment": ["must be a string"]
}
}List events
Returns the events in your workspace, newest first. Every webhook delivery is built from one of these events, so you can use this endpoint to backfill events your endpoint missed. Requires an API key with full scope.
/eventsQuery parameters
pageintegerPage number, starting at 1. Default 1. Pages deeper than 2,500 events ((page - 1) × limit > 2500) return 422.
limitintegerEvents per page, from 1 to 100. Default 100.
typestringEvent type, or a comma-separated list of types, for example email.delivered,email.bounced. Workspace and custom events keep their prefix, for example c:order.completed. See Event types.
scopestringOnly events of these scopes, comma-separated: system (events Emailit records, such as email.delivered), workspace (workspace-defined events, w:<name>) and custom (custom events, c:<name>). For example scope=workspace,custom. Default: every scope. An unknown scope returns 400.
include_databooleanSet to true to include each event’s data payload. Default false, which returns only object, id, type and created_at.
matchstringall (default) requires every filter. or matches any filter. See Filtering.
orderstringSort key for this list. See the sort keys below.
directionstringasc or desc.
Filters and sort
List filters are one layer of key.condition=value query parameters. See Filtering for match, order, direction and the condition list per type.
Filter keys
| Key | Type | Conditions | Notes |
|---|---|---|---|
type | string | exact, not_exact, contains, not_contains, starts_with, ends_with, empty, not_empty | |
created_at | date | exact, before, after, empty, not_empty |
Sort keys
Pass order as one of these keys and direction as asc or desc: type, created_at
Without a created_at filter, the list only covers the last 2 days. To look further back, add one, for example created_at.after=2026-09-01T00:00:00Z. Older events are available for as long as your plan’s data retention keeps them.
Returns
Returns 200 OK with the events in data, plus next_page_url and previous_page_url (null at either end). The page URLs keep type, scope, limit and include_data.
Returns 422 with code: "events_offset_too_large" when the page is too deep. Narrow the date range or the types instead of paging further.
{
"data": [
{
"object": "event",
"id": "evt_2xLc9Rr4Tn6Wb1Qm8Ks3Vf0Hd7a",
"type": "email.delivered",
"created_at": "2026-10-01T10:02:15.912000+00:00"
}
],
"next_page_url": "/v2/events?page=2&limit=100&type=email.delivered",
"previous_page_url": null
}{
"data": [
{
"object": "event",
"id": "evt_2xLc9Rr4Tn6Wb1Qm8Ks3Vf0Hd7a",
"type": "email.delivered",
"data": {
"object": {
"id": "em_2xLc8Mm3Pq5Vn0Ra7Jt2Wd9Fb4e",
"object": "email",
"from": "hello@acme.com",
"to": "ada@example.com",
"subject": "Your order has shipped",
"status": "delivered",
"meta": { "order_id": "1042" },
"updated_at": "2026-10-01T10:02:14.307000+00:00",
"created_at": "2026-10-01T10:02:11.583000+00:00"
}
},
"created_at": "2026-10-01T10:02:15.912000+00:00"
}
],
"next_page_url": "/v2/events?page=2&limit=100&type=email.delivered&include_data=true",
"previous_page_url": null
}{
"error": "Page is too deep. Narrow the type or date filter, or open an earlier page.",
"code": "events_offset_too_large"
}Retrieve an event
Returns one event with its full data payload. The id is the same value webhooks send as event_id, so you can look up any event you received. Requires an API key with full scope.
/events/:idPath parameters
idstringRequiredEvent ID, for example evt_2xLc9Rr4Tn6Wb1Qm8Ks3Vf0Hd7a.
Returns
Returns 200 OK with the event: object (event), id, type, data and created_at. data.object has the same shape as in the webhook payload for that type; see the webhook event reference.
For workspace (w:) and custom (c:) events, data.object holds object (workspace_event or custom_event), name, properties, contact, anonymous_id, occurred_at, source, identity_verified for tracking script events that name a contact, and, when there is one, context. See Send an event.
Returns 404 with error: "Event not found" if the ID doesn’t exist in your workspace.
{
"object": "event",
"id": "evt_2xLc9Rr4Tn6Wb1Qm8Ks3Vf0Hd7a",
"type": "email.delivered",
"data": {
"object": {
"id": "em_2xLc8Mm3Pq5Vn0Ra7Jt2Wd9Fb4e",
"object": "email",
"from": "hello@acme.com",
"to": "ada@example.com",
"subject": "Your order has shipped",
"status": "delivered",
"meta": { "order_id": "1042" },
"updated_at": "2026-10-01T10:02:14.307000+00:00",
"created_at": "2026-10-01T10:02:11.583000+00:00"
}
},
"created_at": "2026-10-01T10:02:15.912000+00:00"
}{
"error": "Event not found"
}