events:write scope, reading needs events:read. Both run inside a tenant, so a workspace key uses the default tenant unless you pass BuzzKit-Tenant.
Tracking from your backend
POST /v1/events takes up to 100 events per call, and a bare event object is accepted as shorthand for a list of one. It answers 202 with the list in input order.
externalId creates the subscriber, the same way a device registration does, and the stream then also carries a $subscriber.created.
Tracking from the app
The iOS SDK posts toPOST /v1/client/events with a client key, in the shape { externalId, identityHash?, source, events }, up to 100 events per call. source is ios, android or web. Identity verification applies as on every client call, and a valid hash stamps the subscriber verified. See Authentication for what a client key can reach.
An offline client keeps its events queued with their original timestamp and drains them in batches when it is back online.
The event
timestamp may be up to seven days in the past, for offline queues, and at most one hour ahead, for clock skew. Anything outside that is a 400 invalid_timestamp. A data payload over the limit is event_data_too_large.
id is a dedupe key, not the event’s id: sending the same id again returns the original event with status: "duplicate" and stores nothing. BuzzKit always assigns its own evt_ id.
Reserved names
Names starting with$ belong to BuzzKit. A $ name sent to POST /v1/events is a 400 reserved_event, and from the app only the SDK’s own names are accepted.
The system events worth knowing
The system events worth knowing
The SDK may send
$app.installed, $app.updated, $app.opened, $app.backgrounded, $session.ended, $notification.delivered, $notification.opened, $notification.dismissed, $activity.started, $activity.ended, $activity.dismissed, $activity.stale, $local.scheduled, $deeplink.opened, $action.triggered, $permission.changed and $identify.The engine writes $subscriber.created, $subscriber.updated, $subscriber.deleted, $subscription.registered, $subscription.muted, $subscription.unmuted, $subscription.removed, $subscription.invalidated, $preferences.updated, the $run.* events of a workflow run, and $send.$subscriber.created is written once, on whichever call first sees the externalId. $subscription.muted and $subscription.unmuted are written only when enabled actually changes, and $preferences.updated only when a preference actually changes.$app.opened can trigger a workflow, $notification.opened can decide a branch, and both sit on the timeline alongside everything you track.
Reading
Reads come from the event stream, not from Postgres, so they are seconds behind writes and paginate by keyset rather than by page number.counts for the last 24 hours, 7 days, 30 days and all time, along with subscribers7d, the sources and providers behind it, and firstAt and lastAt. Volume takes ?range=24h|7d|30d and returns buckets with a count and a subscriber count each, with empty buckets omitted.
GET /v1/events carries no total. It is a keyset over the stream rather than a counted table, so you page it with the previous response’s nextCursor. Every other convention, the envelope and the { items, hasMore, nextCursor } list shape, is unchanged.Embedding the stream
GET /v1/events/token returns a short-lived JWT with a url and an expiresAt, valid for an hour, that reads the catalog, volume, recent and timeline endpoints for one tenant. The tenant is fixed in the token and cannot be overridden, and the token cannot ingest, so it is safe in a browser that renders those views.
Next
Segments
Turn events into conditions that pick an audience at send time.
Workflows
React to an event with waits, branches and sends.
Sources
Turn another service’s webhooks into events with no code.