Skip to main content

Analytics

Analytics events are captured for a wide range of card activity and user actions. There are four ways to extract these analytics from Atomic:

  1. Batched data
  2. Real-time data
  3. Workbench
  4. Webhooks

Atomic provides the ability to download analytic events from the previous 30 days directly in the Workbench. We also provide two API endpoints which allow analytics events to be retrieved from the Atomic Platform.

Backup and storage

Analytics are stored in Atomic for the amount of time defined in an organization's data retention settings.

warning

Atomic is not a long-term storage solution. If you wish to store Analytic data for a long-term (i.e. longer than a year) then you should use the APIs as documented in this page to retrieve the data and store it in your own solution. The recommended pattern is to poll the batched analytics API to download and copy analytics out.

Batched data analytics via API

See Atomic's public API specification for more specific details about the analytics/batched endpoint.

Analytics data is processed in hourly batches, and available for retrieval as CSV, newline-delimited JSON or parquet from the Atomic API. Batches are available at most one hour after their period ends. The default format is newline-delimited JSON, if none is specified.

A time range may be specified via the from and to parameters, which are timestamps in ISO8601 format. Note that data is processed hourly, so it isn't possible to retrieve a partial hour. When creating the date range, the from parameter is rounded down to the nearest hour, and to is rounded up. The maximum allowed date range is 30 days.

GET /v1/:environmentId/analytics/batched?from=<timestamp>&to=<timestamp>&format=<ndjson | parquet | csv>

The API response contains an array of pre-signed urls pointing to the data files for the specified period, these URLS expire after 15 minutes. The files are grouped by hour ended in UTC, using the format YYYY-MM-DDTHH - for example, the batch for March 27 2020, 1-2am will be under 2020-03-27T02. Note the file array for an hour will only appear here once it is finished processing. If the file array is empty, it means there were no analytics events for that period.

For example, to retrieve all analytics data for the 27th of March 2020:

GET /v1/:environmentId/analytics/batched?from=2020-03-27T00:00:00Z&to=2020-03-28T00:00:00Z&format=parquet

Response:

{
"data": {
"2020-03-27T01": ["https://data.atomic.io/path-to/file.parquet?signature=..."],
"2020-03-27T02": ["https://data.atomic.io/path-to/file.parquet?signature=..."],
...
"2020-03-28T00": ["https://data.atomic.io/path-to/file.parquet?signature=..."],
}
}

Note that url parameters may be localized, but the response format will always use UTC.

Newline-delimited JSON format

Files contain one analytics object, in json format matching the schema, per line. For example

{"id": "...", "analyticsEvent": "card-published", "endUserId": "...", "timestamp": "...", "eventContext": { ... }, ...}
{"id": "...", "analyticsEvent": "card-published", "endUserId": "...", "timestamp": "...", "eventContext": { ... }, ...}
{"id": "...", "analyticsEvent": "card-published", "endUserId": "...", "timestamp": "...", "eventContext": { ... }, ...}

Parquet format

Each record in the parquet file has the keys in the schema flattened.

Real-time data analytics via API

See Atomic's public API specification for more specific details about the analytics endpoint.

Another endpoint is available to retrieve non-batched analytics in real-time. Data for the last 7 days may be retrieved in JSON format, with the oldest data returned first. The from and to parameters are timestamps in ISO8601 format. The analytics in the API response can be further filtered with the following query parameters:

  • cardTemplateId
  • eventName (can be a single event or comma separated list i.e. card-completed or card-completed,card-snoozed)
  • endUserId
  • flowConfigId
  • flowInvocationId
  • desc (order newest to oldest, providing any value in this parameter will enable this)
  • limit (default 1000, max 5000)
warning

Analytic events are written to Atomic's datastore from a queue based system. As such, at times of high usage there may be a delay in writing analytic events and therefore also a delay in retrieving them from this endpoint. Typically such a delay would not be more than a few minutes. Due to this, we recommend, if polling this endpoint, to leave a buffer between the current time and the time you are querying for of at-least a few minutes.

Atomic does not recommend polling this endpoint to retrieve all of your analytic events, the batched analytics endpoint is more fault tolerant and recommended.

GET /v1/:environmentId/analytics?from=<timestamp>&to=<timestamp>

The additional query parameters listed above are optional, they can be used like so:

GET /v1/:environmentId/analytics?from=<timestamp>&to=<timestamp>&cardTemplateId=<your-card-template>&limit=3000

Response format:

{
"data": {
"events": [
{ ... },
{ ... },
...
],
"count": 100, // number of events returned in this payload
"cursor": "..." // used for pagination
}
}

Pagination

When you make a request to get events a cursor property is returned. To get the next page of results, use the cursor query parameter with the value returned from your last request to get the next page i.e. cursor={cursor from previous request}.

Workbench download

Batches are available in JSON, CSV, or Parquet format.

Note that CSV exported content is sanitized as per OWASP guidance.

Locate them in your Workbench: Tools > Operations > Analytics exporter.

This area of the Workbench is updated a few minutes after the hour, to include the preceding hour’s analytics. The Timestamp field indicates when the hour period began. The amount of analytic events in each batch is limited. If the number of analytic events emitted within an hour is greater than the limit, multiple batches are created and saved into multiple files available for download.

A description of all of the available analytics events are available in the Analytics Reference.

Batches are only available for the past 30 days. If you require analytics data from prior to this date, please use our API Endpoints.

Webhooks

Another pattern we support is to subscribe a URL to card status-change event. Atomic will then send an update for the event (e.g. card-dismissed) with the payload data to your URL.

Docs on configuring webhooks are found here: Webhooks

Analytics export settings

Workbench members with access to the 'Environments' resource can configure the analytics data content returned by Atomic.

Test cards and request payloads are included in analytics data by default. Card response data is not included by default.

The settings are all configured at the environment level.

info

Environment analytics data preferences affect API responses, but not webhooks. Webhooks get the full payload by default, but by using our mapping capability, you can configure webhook data to match your desired output.

Change environment analytics export settings

To change your environment analytics export settings:

  1. Go to Configuration > Webhooks > Analytics Export Settings.
  2. Toggle the preferences to your desired settings, and select ‘Save’.
info

With batched analytics, the settings are applied at the time the batch is generated. This means that no historical batches will be changed.

Setting the preferences

The analytics export settings that can be configured on or off:

PreferenceDescriptionDefault setting
Include test cardsTest cards that are manually triggered from within the Workbench are included. These are tagged as Test within the Workbench card log.on
Include request payloadPayload data is included, and captured in the platformContext.eventDetail object e.g. within a card-completed analytics event.on
Include card responseResponses from customers are included, and captured in the properties.values object e.g. within a card-displayed, notification-sent, or notification-received analytics event.off
Card response data analytics

The card response data payload is only available via the analytics API endpoints. The workbench analytics debugger screen will not include any card response data. This is done to prevent potential customer personally identifiable information (PII) being exposed in the workbench.

Custom events

It is possible to send your own analytic events to the Atomic platforms via the Atomic API or SDKs.

After a custom event has been sent to Atomic once it can be used to:

  • Create Webhooks that trigger when instances of that event are received.
  • Create user segments; users associated with the event will get the profile analytics fields last_[your-custom-event-name]_at & first_[your-custom-event-name]_at populated. This allows you to build segments based on custom events.
  • In the future Action Flows will be able to be configured to start or resume when custom analytics occur with particular properties.

Known custom events and their properties can be seen at Configuration > Webhooks > Custom events. Each time a new custom event type is sent to Atomic it is automatically added to this page.

Instances of custom events are listed in the analytics debugger tool, but are not returned in the real-time or batched analytics responses.

Sending custom events via API

A credential role of events is required to utilize the Analytics API.

You can interact with the Analytics API using Insomnia (follow the Insomnia instructions) or curl. When using curl, you'll need to set up your Authentication first.

Detailed specs and examples can be found in the Atomic API spec for the Analytics endpoint.

ENDPOINT="https://$ORG_ID.customer-api.atomic.io/v1/$ENVIRONMENT_ID/analytics"

PAYLOAD='{
"data": [
{
"id": "6a1e92d3-fa0d-56da-a2d7-bc616ade57d1",
"eventName": "my-custom-event",
"eventType": "custom",
"endUserId": "an-end-user",
"timestamp": "2022-04-25T02:47:46.977Z",
"properties": {
"stringProperty": "string",
"boolProperty": true,
"numberProperty": 42,
"objectProperty": {},
"arrayProperty": []
},
"cardContext": {
"cardTemplateId": "a-card-template-id",
"cardInstanceId": "a-card-instance-id"
},
"platformContext": {
"flowInstanceId": "dec19d46-241d-47e9-906b-6361ac49903a"
"flowInvocationId": "aab5dfd1-aa7b-4ef5-8955-66045028e616"
"flowConfigId": "5ce06f73-4bd1-436c-b5a1-a0fa0a0f0bf1"
}
}
]
}'

# send request to Atomic
curl -X POST "$ENDPOINT" \
--header "Content-Type:application/json" \
--header "Authorization: Bearer $TOKEN" \
--data "$PAYLOAD"

Request information

  • The required fields are:
    • id
    • eventName
    • eventType
    • endUserId
    • timestamp
  • id must be a valid uuid.
  • If the custom event is in-use on a card-scoped webhook then cardContext must be included in order for that webhook to trigger.
  • The properties object contains the fields which are shown in Configuration > Webhooks > Custom events and will be used for configuring Action Flows in future. The fields in properties are entirely custom, they should be used to add any metadata associated with your custom event that is useful to you. If the data within properties changes between instances of an event then they are updated in the Configuration > Webhooks > Custom events screen - this screen will always reflect the latest instance's properties.
  • platformContext and the flow IDs within are required if a custom event is used to resume an Action Flow.

Sending custom events via SDK

It is also possible to send custom events via SDK:

Analytics events examples

See the examples for each of the analytic events page for payload examples. The response payloads in your environment might look different, depending on your analytics settings.

Analytics event schema

{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"title": "Atomic Analytics Schema",
"description": "The root schema comprises the entire JSON document.",
"required": ["id", "endUserId", "analyticsEvent", "timestamp"],
"properties": {
"id": {
"type": "string",
"title": "Event id",
"description": "A GUID used for deduping"
},
"endUserId": {
"type": "string",
"title": "End user id",
"description": "The end user (customer) identifier known to Atomic.",
"examples": ["019mr8mf4r"]
},
"analyticsEvent": {
"type": "string",
"enum": [
"card-cancelled",
"card-published",
"card-embargoed",
"card-dismissed",
"card-error",
"card-completed",
"card-snoozed",
"card-unsnoozed",
"card-expired",
"notification-sent",
"notifications-not-sent",
"stream-displayed",
"card-displayed",
"card-subview-displayed",
"card-subview-exited",
"user-redirected",
"runtime-vars-updated",
"notification-received",
"card-voted-up",
"card-voted-down",
"snooze-options-displayed",
"snooze-options-canceled",
"request-failed",
"sdk-initialized",
"segment-entered",
"segment-exited",
"video-played",
"video-completed",
"action-flow-started",
"action-flow-step-completed",
"action-flow-step-failed",
"action-flow-completed",
"action-flow-failed",
"a-custom-event"
],
"title": "Analytics event name",
"description": "Name of the analytics event.",
"examples": ["card-published"]
},
"eventName": {
"type": "string",
"enum": [
"card-cancelled",
"card-published",
"card-embargoed",
"card-dismissed",
"card-error",
"card-completed",
"card-snoozed",
"card-unsnoozed",
"card-expired",
"notification-sent",
"notifications-not-sent",
"stream-displayed",
"card-displayed",
"card-subview-displayed",
"card-subview-exited",
"user-redirected",
"runtime-vars-updated",
"notification-received",
"card-voted-up",
"card-voted-down",
"snooze-options-displayed",
"snooze-options-canceled",
"request-failed",
"sdk-initialized",
"segment-entered",
"segment-exited",
"a-custom-event"
],
"title": "Analytics event name",
"description": "Name of the analytics event.",
"examples": ["card-published"]
},
"timestamp": {
"type": "string",
"title": "Timestamp",
"description": "UTC timestamp of when the event occurred.",
"examples": ["2012-12-02T00:30:12.984Z"]
},
"properties": {
"type": "object",
"title": "Properties schema",
"description": "An object containing extra content detail about this particular event.",
"properties": {
"previousStatus": {
"type": "string",
"title": "Previous status",
"enum": ["suppressed", "published"],
"description": "Used with any status change event to indicate the previous status"
},
"notificationScenario": {
"type": "string",
"title": "Notification scenario",
"enum": ["newCard", "unsnoozedCard"],
"description": "Used for the 'notification-sent' event to indicate whether the notification was sent for a new or snoozed card"
},
"rescheduledBasedOnEndUserPrefs": {
"type": "boolean",
"title": "Rescheduled based on end user prefs",
"description": "Used for the 'notification-sent' event to indicate if the notification was rescheduled due to user delivery preferences"
},
"subviewId": {
"type": "string",
"title": "Subview id",
"description": "Used for the 'card-subview-displayed' and 'card-subview-exited' events to identify the relevant subview"
},
"subviewTitle": {
"type": "string",
"title": "Subview title",
"description": "Used for the 'card-subview-displayed' and 'card-subview-exited' events"
},
"subviewLevel": {
"type": "string",
"title": "Subview level",
"description": "Used for the 'card-subview-displayed' and 'card-subview-exited' events event to identify the subview level"
},
"linkMethod": {
"type": "string",
"title": "Link method",
"enum": ["url", "payload"],
"description": "Used for the 'user-redirected' event to identify the link method used."
},
"detail": {
"type": "string",
"title": "Detail",
"enum": ["image", "linkButton", "submitButton", "textLink"],
"description": "Used for the 'user-redirected' event to identify the type of the card component that triggers this event."
},
"url": {
"type": "string",
"title": "URL",
"description": "Used for the 'user-redirected' event, when the url method is used, to indicate the URL that was launched."
},
"payload": {
"type": "string",
"title": "Payload",
"description": "Used for the 'user-redirected' event, when the payload method is used, to indicate the payload that was sent to the SDK."
},
"resolvedVariables": {
"type": "object",
"title": "Resolved variables",
"description": "Used for the 'runtime-vars-updated' event to express which runtime variables were updated and their new values."
},
"source": {
"type": "string",
"title": "The source of a card action",
"description": "Used for the 'card-voted-down/card-voted-up' event to identify the source of the vote.",
"enum": [
"overflow-menu",
"card-button"
]
},
"reason": {
"type": "string",
"title": "Downvote reason",
"enum": ["not-relevant", "too-often", "other"],
"description": "Used for the 'card-voted-down' event to identify the reason for the vote."
},
"message": {
"type": "string",
"title": "Downvote message",
"description": "Used for the 'card-voted-down' event. Contains a message from the user."
},
"snoozePeriod": {
"type": "string",
"title": "Snooze period",
"description": "Used for the 'card-snoozed' event, specifies how long the card was snoozed for as an ISO8601 time interval"
},
"path": {
"type": "string",
"title": "Path",
"description": "Used for the 'request-failed' event, specifies the path for the failed request"
},
"statusCode": {
"type": "integer",
"title": "Status code",
"description": "Used for the 'request-failed' event, specifies the http status code of the failed request"
},
"values": {
"type": "object",
"title": "Card response values",
"description": "Contains response values for the 'card-completed' event"
}
}
},
"eventContext": {
"type": "object",
"title": "The eventContext Schema",
"description": "An object containing metadata about this particular event.",
"properties": {
"ip": {
"type": "string",
"title": "IP",
"description": "The IP address associated with the end user if available"
},
"actionSource": {
"type": "string",
"title": "Action Source",
"enum": ["user", "customer", "internal"],
"description": "The source of the action"
},
"userLocalTimestamp": {
"type": "string",
"title": "User Local Timestamp",
"description": "The local timestamp associated with the user device"
}
}
},
"sdkContext": {
"type": "object",
"title": "The sdkContext Schema",
"description": "Info about the SDK related to this event.",
"properties": {
"platform": {
"type": "string",
"title": "Platform",
"enum": ["Web", "iOS", "Android"],
"description": "The customer platform"
},
"sdkVersion": {
"type": "string",
"title": "SDK Version",
"description": "The version of the Atomic SDK"
},
"sdkDevice": {
"type": "string",
"title": "SDK sdkDevice",
"description": "The customer Device"
},
"sdkTimezone": {
"type": "string",
"title": "SDK sdkTimezone",
"description": "The time zone on the customer device"
},
"containerId": {
"type": "integer",
"title": "Container Id",
"description": "The ID of the container that was in context when the event occurred"
},
"isDnDActive": {
"type": "boolean",
"title": "Is Do Not Disturb Active",
"description": "Whether DnD was active at the time the event occurred"
}
}
},
"streamContext": {
"type": "object",
"title": "The streamContext Schema",
"description": "Details of the stream at the time the event occurred.",
"properties": {
"streamId": {
"type": "string",
"title": "Stream Id",
"description": "The identifier for the stream with which the event is associated"
},
"streamName": {
"type": "string",
"title": "Stream Name",
"description": "The name of the stream"
},
"streamLength": {
"type": "integer",
"title": "Stream Length",
"description": "The length of the stream"
},
"streamLengthVisible": {
"type": "integer",
"title": "Stream Length Visible",
"description": "The visible length of the stream"
},
"cardPositionInStream": {
"type": "integer",
"title": "Card Position In Stream",
"description": "The card position in the stream, indexed at 1"
},
"displayMode": {
"type": "string",
"title": "Display Mode",
"enum": ["stream", "single"],
"description": "Used for the 'stream-displayed' event to indicate the mode in which the stream was displayed"
}
}
},
"cardContext": {
"type": "object",
"title": "The cardContext Schema",
"description": "Details of the card in the context of this event",
"properties": {
"cardInstanceId": {
"type": "string",
"title": "Card Instance Id",
"description": "The card instance id"
},
"cardPriority": {
"type": "number",
"title": "Card Priority",
"description": "The priority assigned to the card",
"enum": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10
]
},
"cardInstanceStatus": {
"type": "string",
"title": "Card Instance Status",
"enum": [
"embargoed",
"active",
"snoozed",
"dismissed",
"completed",
"expired"
],
"description": "The current status of the card"
},
"cardViewState": {
"type": "string",
"title": "Card View State",
"enum": ["topview", "subview"],
"description": "The current active view of the card at the time of the event"
},
"cardTemplateSettings": {
"type": "object",
"title": "Card Template Settings",
"description": "The card template settings",
"properties": {
"sendNotifications": {
"type": "boolean",
"title": "Send Notifications",
"description": "Whether notifications are on or off for the card template"
}
}
},
"cardTemplateId": {
"type": "string",
"title": "Card Template Id",
"description": "The card template id"
},
"cardTemplateName": {
"type": "string",
"title": "Card Template Name",
"description": "The card template name"
},
"cardTemplateVersion": {
"type": "integer",
"title": "Card Template Version",
"description": "The card template version"
},
"cardPresentation": {
"type": "string",
"title": "Card Presentation",
"enum": ["bundle", "individual"],
"description": "The card presentation mode"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"title": "Published At",
"description": "The time when the card's status first transitioned to \"active\""
}
}
},
"platformContext": {
"type": "object",
"title": "The platformContext Schema",
"description": "Details of the platform in the the context of this event.",
"properties": {
"eventDetail": {
"type": "object",
"title": "Event Detail",
"description": "The event data"
},
"targetStreamIds": {
"type": "array",
"title": "Target stream IDs",
"description": "The target streams for a card instance",
"items": {
"type": "string"
},
"uniqueItems": true,
"default": []
},
"targetPlatforms": {
"type": "array",
"title": "Target platforms",
"description": "The target platforms for a card instance",
"items": {
"type": "string"
},
"uniqueItems": true,
"default": []
},
"metadata": {
"type": "object",
"title": "Event metadata",
"description": "Arbitrary metadata passed through from the originating API call"
},
"eventNotificationDetail": {
"type": "object",
"title": "Event Notification Detail",
"description": "The event notification data"
},
"lifecycleId": {
"type": "string",
"title": "Lifecycle Id",
"description": "The event lifecycleId"
},
"triggerEventSource": {
"type": "string",
"title": "Trigger Event Source",
"enum": ["live", "test"],
"description": "The trigger event source"
},
"flowInstanceId": {
"type": "string",
"title": "Flow Instance Id",
"description": "The instance Id for the Action Flow that triggered this event"
},
"flowInvocationId": {
"type": "string",
"title": "Flow Invocation Id",
"description": "The invocation Id for the Action Flow that triggered this event"
},
"flowConfigId": {
"type": "string",
"title": "Flow Config Id",
"description": "The Config Id for the Action Flow that triggered this event"
},
"flowVersion": {
"type": "number",
"title": "Flow Version",
"description": "The version of the Action Flow that triggered this event"
},
"cardCreationStepId": {
"type": "string",
"title": "Card Creation Step Id",
"description": "For card-centric events, the Id of the Action Flow step which created the card"
}
}
}
}
}

Example parquet record as JSON

Note that the parquet files flatten the keys of the above schema.

{
"id": "6ed520b7-c313-5e98-afba-8cea41fc15b1",
"endUserId": "f7c75a4f-55f5-40a1-b55c-9783d9823fe6",
"analyticsEvent": "card-published",
"timestamp": "2020-07-24T02:50:28.526Z",
"eventContext.actionSource": "internal",
"sdkContext.isDNDActive": false,
"streamContext.streamId": "TspKk",
"streamContext.streamName": "Slurm Soda Co",
"cardContext.cardInstanceId": "leaveRequest|live|647a444a-43a3-484d-968d-c310686f6f01|8",
"cardContext.cardInstanceStatus": "active",
"cardContext.cardTemplateId": "8Xlss",
"cardContext.cardTemplateName": "Leave request",
"cardContext.cardTemplateVersion": 1,
"cardContext.cardTemplateSettings.sendNotifications": false,
"platformContext.eventDetail": "eyJhIjoiZXhhbXBsZSJ9",
"platformContext.lifecycleId": "647a444a-43a3-484d-968d-c310686f6f01",
"platformContext.triggerEventSource": "live",
"platformContext.flowInstanceId": "bb9c4ed5-d97c-49e7-be4c-97f9d88cbb9f",
"platformContext.flowInvocationId": "caaa170d-0521-4dfa-903a-19f410480402",
"platformContext.flowConfigId": "A9WBVx",
"platformContext.cardCreationStepId": "create_card_L9qk",
}