EventsMenu
On this page

Events

Updated

Name events and their properties, fill the context object, and use the events with a fixed meaning.

An event records something a contact did: signed up, viewed a page, completed an order. This page is Kirora’s event spec: how to name events and their properties, what the context object holds, and the events with a fixed meaning.

What an event is

Every event has:

Field What it holds
name What happened, such as Order Completed
properties Details of this occurrence, such as { "order_id": "10042", "total": 125.5 }
occurredAt When it happened, as ISO 8601 with Z or an offset. Defaults to now, and may be at most 24 hours ahead
Idempotency key Optional, and recommended: your id for this occurrence. A retry with the same key records nothing new; without one, every request records a new event
context Where and on what it happened: the page, the device, the visit

Events come from two places:

  • Your server, through POST /v1/events/track, or POST /v1/events/batch for up to 100 at once. Name the contact by exactly one of contactId, externalId or email. The contact must already exist: an event for an unknown contact is refused with 404 and dropped, so identify the contact first.
  • Kirora’s web SDK, which tracks events for the user signed in to your site.
JSON
{
  "externalId": "customer-881",
  "name": "Order Completed",
  "properties": { "order_id": "1042", "total": 46.5, "currency": "USD" },
  "occurredAt": "2026-10-24T09:30:00.000Z"
}

A track answers 201 with a receipt, not the event: its id, the occurredAt it was stored with (the time you sent, or the time it arrived when you sent none) and its expiresAt.

An event that arrives more than 72 hours after its occurredAt starts no journey: it is history being backfilled, such as an order import. It is still stored, and still counts for segments, journey exits and waits.

Both count against the plan’s monthly event allowance (1,000 events on Free). Past it, a paid plan draws extra events from the prepaid balance; with none left, events are still accepted up to the plan’s tolerance, then each is refused with 403 PLAN_LIMIT_REACHED and listed in the event log. Free stops at its allowance. A replay of the same idempotency key, an order’s line events and Kirora’s own events never count.

Segments, journeys and conversion goals all read events. A segment can match contacts who did an event, how often and with which properties. A journey can start when an event arrives, and its messages can read the event’s properties with {{ event.properties.<name> }}. A conversion goal counts an event as a conversion, and can add up one of its properties as revenue.

How long events are kept

An event is kept for the plan’s event retention, counted from when it happened: 30 days on Free, up to a year on paid plans. Its expiresAt is fixed when it is stored, so changing plan moves only the expiry of events stored afterwards: an upgrade never lengthens history you already have, and a downgrade never shortens or hides it. An event that happened longer ago than the plan keeps events is refused with 400 VALIDATION_ERROR on occurredAt, since it would expire at once.

Naming

Event names

  • Title Case, object then past-tense action: Plan Upgraded, Order Completed, Invoice Paid. Not upgrade_plan, orderCompleted or Completed Order.
  • One fixed name per action. Anything that varies, such as an id, a price or a plan, goes in properties, never in the name. Product Viewed with { "product_id": "8812" }, not Viewed product 8812.
  • Names are case-sensitive. order completed does not start a journey waiting for Order Completed.
  • Up to 128 characters, no control characters (newlines, tabs). Leading and trailing spaces are removed: Order Completed is stored as Order Completed.
  • At most 1,000 distinct names per workspace, names defined before they are sent included. A new name past that is refused with EVENT_NAME_LIMIT_REACHED. GET /v1/events/catalog reports the count in meta.nameUsage. DELETE /v1/events/catalog/{name} removes a name sent by mistake, with all its events, unless a journey, segment, campaign or goal still reads it; see Deleting a name.

Property names

  • Lowercase snake_case: order_id, plan_name, from_background. This holds for all event data: an event’s properties, a transactional send’s data, and the properties of Kirora’s own events. The fields the API itself defines (name, occurredAt, contactId) are camelCase.
  • Up to 64 characters per name, and the whole properties object at most 16 KB as JSON.
  • Filter on nested values with a dotted path. Segment rules, journey conditions and conversion goals read a nested value as order.total (a number indexes a list, as in items.0.price). A flat key with exactly that name takes precedence. Templates can also read nested values and repeat a block over a list.
  • Send the same type every time. A total that is sometimes 42.5 and sometimes "42.50" makes rules like “total greater than 40” unreliable.
  • Send amounts in major units: dollars, not cents.

The context object

context sits beside properties and describes where and on what the event happened. Properties belong to your app; context has one definition for every SDK, so a field means the same thing on web and mobile.

Kirora’s web SDK fills context for you. From your server it is optional: send the fields you know, and Kirora stores them as sent. Every field is optional, and a field not listed here is refused. A page or campaign value that is too long is cut, not refused: page.url, page.search and page.referrer at 8,192 characters, page.path at 2,048, page.title and each campaign field at 256. Segments and journeys read properties, not context.

Field Holds Filled by
sessionId The visit the event belongs to Web SDK (mobile SDKs, when they ship)
device.id A random id the SDK keeps in the site’s or app’s own storage. Never a hardware or advertising id. It goes with an uninstall or cleared site data Web SDK (mobile SDKs, when they ship)
locale The user’s language, as BCP 47: en-GB Web SDK (mobile SDKs, when they ship)
timezone The user’s time zone, as IANA: Europe/Berlin Web SDK (mobile SDKs, when they ship)
page.url The page’s canonical URL if it has one, with the page’s query string added unless the canonical URL has its own; else the page’s address. Query string kept, fragment removed Web SDK
page.path The page’s path: /pricing Web SDK
page.search The page’s query string as it is, ?plan=pro, empty or absent when there is none. Always the visited address’s, even with a canonical URL Web SDK
page.title The page’s title Web SDK
page.referrer The full address of the page the user came from, when there is one Web SDK
campaign.source The page’s utm_source Web SDK
campaign.medium The page’s utm_medium Web SDK
campaign.name The page’s utm_campaign Web SDK
campaign.term The page’s utm_term Web SDK
campaign.content The page’s utm_content Web SDK
ip The IP address the request came from Kirora, from the request
userAgent The request’s User-Agent header Kirora, from the request
library.name, library.version The SDK or server library that sent the event: kirora-web, 1.0.0 Kirora, from the request

For events from its SDKs, Kirora sets ip, userAgent and library itself and replaces anything the device sent for them. From your server it adds only library, when the request comes from a Kirora server library (its User-Agent starts with the library’s name and version, such as kirora-node/0.1.0, kirora-go/0.1.0 or kirora-dotnet/0.1.0) and the event has no library of its own; your server’s IP address and user agent say nothing about the contact, so they are not kept.

The web SDK sends page addresses and referrers with their query strings, and masks nothing. If a page’s address can hold a token or an email address (a password-reset link, say), remove it from the address before the page is tracked, for example with history.replaceState.

Reserved names

Kirora’s web SDK tracks the events under Standard events, and Kirora gives those names their meaning: the same name means the same thing in every workspace. You may send them yourself, from your server or with track(), but use the properties documented here.

Names beginning with contact. are Kirora’s own. Tracking one is refused with VALIDATION_ERROR, and they cannot be deleted.

Standard events

Session Started

A visit begins. The web SDK tracks it unless you turn sessions off. A visit ends after 30 minutes with no activity.

Property Type Notes
path string Web: the landing page’s path
referrer string Web: the full address of the page the user came from
utm_source, utm_medium, utm_campaign, utm_term, utm_content string Web: each present on the landing page’s address

The mobile SDKs, when they ship, will track Session Started without properties.

Page Viewed

Web: a page was viewed. The web SDK tracks it when you call trackPageView(), or on every page once you turn on automatic page views.

Property Type Notes
url string As page.url: the canonical URL if the page has one, else the page’s address, query string kept
path string The page’s path
search string As page.search: the page’s query string; empty or absent when there is none
title string The page’s title
referrer string The full address of the page the user came from, when there is one

Screen Viewed

Mobile: a screen was viewed. Your app calls trackScreenView(name); screens are never tracked automatically.

Property Type Notes
name string The screen’s name, as your app calls it: Checkout

Application lifecycle

Mobile: tracked when your app turns on lifecycle events. Each is off until then.

Event When Properties
Application Installed The first launch after install version, build
Application Opened Each launch, and each return to the foreground from_background, version, build
Application Updated The first launch of a new version version, build, previous_version, previous_build
Application Backgrounded The app leaves the foreground None
Property Type Notes
version string The app’s version: 2.4.0
build string The app’s build number: 412
from_background boolean true when the app returned from the background, false on a fresh launch
previous_version string The version before the update
previous_build string The build before the update

Commerce

Product, cart, checkout and order events have their own page: E-commerce events.

Kirora’s own events

Kirora records these itself. Segments and journeys can use them like any event: start a welcome journey on contact.form_submitted, or build a segment of contacts added to a list this week. You cannot track them, and they do not count toward the 1,000 names.

Event When Properties
contact.added_to_list A contact was newly added to a list list_id, list_name
contact.entered_segment A contact began matching a segment, found within 15 minutes segment_id, segment_name
contact.exited_segment A contact stopped matching a segment segment_id, segment_name
contact.field_updated A stored contact field changed value field, value, previous_value
contact.form_submitted A form submission was accepted for a contact form_id, form_name, outcome, source
  • contact.field_updated names the field as firstName, or attributes.<key> and dates.<key> for your own fields. For a masked field, the event carries only { "field": "attributes.<key>", "masked": true }, never its values.
  • contact.form_submitted has outcome created for a new contact and updated for an existing one, and source is the form’s own source label.

Reading events

Every read takes an event’s name as a query parameter, never in the path, since a name may hold /, % or #. Encode it as any query value: ?name=Order%20Completed. Reading needs events:read.

The event object

GET /v1/events and GET /v1/contacts/{id}/events return events in one shape, split on status. An accepted event:

JSON
{
  "id": "0192f3a4-…",
  "status": "accepted",
  "name": "Order Completed",
  "contact": { "id": "0192f3a1-…", "externalId": "customer-881", "email": "[email protected]" },
  "properties": { "order_id": "1042", "total": 46.5, "currency": "USD" },
  "context": {
    "library": { "name": "kirora-web", "version": "1.4.0" },
    "page": { "url": "https://shop.example/checkout" }
  },
  "source": "web",
  "occurredAt": "2026-10-24T09:30:00.000Z",
  "receivedAt": "2026-10-24T09:30:01.204Z"
}

A rejected event is one the API refused. It has no contact, properties or context; it carries the request as sent in payload (null when it was over 32 KB) and the error the API answered. Its name and occurredAt are null when the request had no valid one.

source says how the event came in. Kirora sets it from the path the request took, never from the payload:

source The event came from
api Your server, with an API key
web Kirora’s web SDK
system Kirora itself: Kirora’s own events
segment A Segment source or destination, through /segment/v1
stripe The Stripe connection
zapier, make, n8n A key made for that app

A key made for an app labels its events with that app, whoever uses the key: an event sent with a Zapier key from your own server still says zapier. The SDK’s name and version are in context.library.

The event log

GET /v1/events lists every event the workspace tracked in the last 30 days, accepted or rejected, newest first: did your site’s event arrive, and if not, why? Filter by name, status (accepted or rejected), source, occurredAfter and occurredBefore, or search with query for an event name or a contact’s id, email address or external id. GET /v1/contacts/{id}/events lists one contact’s accepted events, filtered the same way by name, occurredAfter and occurredBefore.

The catalogue

GET /v1/events/catalog lists your workspace’s event names: the ones Kirora records (origin: "system"), and each name you have sent or defined, standard when it is one of Kirora’s standard names and custom otherwise. Each comes with its description, when it was last seen (lastSeenAt), its events in the last 30 days (last30DaysCount) and how many journeys, segments, campaigns and goals read it (usedByCount); recorded is false for a name with no events yet. The figures are counted at most 15 minutes before meta.countedAt, and a name sent for the first time is listed at once.

query (part of the name, any case), origin and recorded filter, and sort orders, over the whole catalogue rather than one page: -lastSeenAt (the default, names never seen last), -last30DaysCount, name, or any of them reversed. The first page carries meta.nameUsage.

GET /v1/events/standard-names lists the standard names you have not sent yet, with what each means: the e-commerce names (commerce) and, for a product contacts sign up to, Signed Up, Signed In, Account Created, Invite Sent, Trial Started and Trial Ended (saas), as Segment’s specs name them. Offer them beside the catalogue in a picker; once you send one, it moves to the catalogue.

Defining a name before you send it

PUT /v1/events/catalog/{name} with { "description": "…" } adds a name before your app sends it, so a journey, segment or goal can be built on it while your developers add the tracking. Send events with exactly that name and its figures fill in. The same call sets or changes the description of any name already listed (null removes it); a description is shown on the Data index and the event’s page, and lets Kiro explain the event. A name new to the workspace counts against the 1,000-name limit. It takes events:write.

An event’s summary and series

GET /v1/events/summary?name=… says whether an event is alive before you build on it: when it last happened (lastSeenAt), how many happened in the last 30 days (counted up to 100,000; last30Days.countExact is false past it), the property keys its latest 1,000 events carry with sample values, and the journeys, segments, campaigns, conversion goals and active workspace goals that read it (up to 50 of each; usedBy.hasMore says when there are more). A workspace goal reads every event its measure names: the counted event, a conversion’s entry and success, a ratio’s two sides and a cohort’s population rules. A retired goal is not listed: its history is settled and it never measures again. A custom name neither tracked nor defined is a 404.

GET /v1/events/series?name=…&interval=day&from=2026-10-01&to=2026-10-25 counts accepted events per hour or day in the workspace timezone, without gaps and at most 366 points, each named by the instant its bucket starts (startsAt). A line that falls to zero after a release shows tracking that broke, which a total hides. Refused events are not counted: read them in the log with ?status=rejected&name=….

Deleting a name

DELETE /v1/events/catalog/{name} deletes a name sent by mistake and every event tracked under it. It answers 202: the name leaves the catalogue and the 1,000-name limit at once, and its events are removed within the hour; until then tracking the name returns 409 CONFLICT. URL-encode the name in the path (Order%20Completed). A name a journey, segment, campaign, conversion goal or active workspace goal reads is refused with 409 IN_USE, naming them in details.usedBy (workspace goals in usedBy.goals). It cannot be undone. A name you defined but never sent just leaves the catalogue. It needs events:delete, a scope apart from events:write so a key that sends events cannot delete them, and which agent keys cannot hold.

Next steps