Data Export

Datasets

This page describes each of the four datasets emitted by Data Export and the shape of records within them. Every record also carries the common envelope fields (id, version, roverID, emittedAt); the tables below describe fields beyond that envelope.


fan_profiles

A fan profile is Rover's representation of a single end-user known to your workspace: the person behind a mobile-app install, an inbound API sync, or a CSV import. Each profile has a stable canonical roverID (UUID) and a flat traits map carrying every attribute the workspace knows about that person: standard attributes (email, first/last name, phone) alongside any custom traits your team has defined (loyalty tier, favorite team, lifetime spend, opt-in flags, and so on).

A record is emitted whenever a profile is created or updated, and the full current trait payload ships on every update; there is no per-field delta. A profile that has had ten distinct trait changes produces ten records, each carrying the complete trait map as it stood at that moment. The most recent record (largest updatedAt) is the source of truth for that fan. Removing a trait surfaces as the key being absent from the next record's traits map.

Profiles are never hard-deleted in this stream. If a profile is unified into another via identity resolution, the merged-away profile simply stops receiving updates; the merge itself surfaces in fan_profile_merges, not here. There are no delete records for fans.

Fan deletions are out-of-band

Fan-level deletions are not signaled in fan_profiles. Identifier removals surface in fan_identity_graph as delete operations, but the underlying fan profile itself simply stops receiving updates. Coordinate fan deletion workflows (for example, propagating right-to-be-forgotten (GDPR / CCPA) requests to your warehouse copy of this data) through your usual Rover support channel.

FieldTypeNotes
idstringDeterministic record id.
versionnumber1.
roverIDstring (UUID)Canonical fan identifier.
traitsobjectFlat map of string → string | number | boolean | string[]. May be empty ({}). Keys are a mix of system traits and any custom traits your team has defined.
updatedAtstring (ISO 8601 UTC)When the profile was last modified upstream. Use this for chronological ordering / "latest record wins" loads.
emittedAtstring (ISO 8601 UTC)When the record was written into the file.
{
  "id": "9f893e451b69b0d1e4d16bc34a8f3fa8",
  "version": 1,
  "roverID": "f41657dd-cc7f-40ef-9198-95215791afb1",
  "traits": {
    "name": "Ada Lovelace",
    "email": "fan@example.com",
    "city": "Toronto",
    "country": "Canada",
    "firstSeen": "2025-09-14",
    "lastSeen": "2026-05-02",
    "deviceOS": "iOS",
    "tags": ["season-ticket-holder", "newsletter"],
    "loyaltyPoints": 1240,
    "favoriteTeams": ["Toronto FC", "Raptors"]
  },
  "updatedAt": "2026-05-02T17:15:09.241Z",
  "emittedAt": "2026-05-02T17:15:10.103Z"
}

System traits

Every workspace ships with a set of system traits that Rover populates automatically from sync, SDK telemetry, identity resolution, and computed fields. They appear inside the traits map alongside your custom traits and use the keys below verbatim. Not every key appears on every fan; a trait Rover has no value for is omitted, not emitted as null.

KeyDescriptionWire type
nameFull name.string
userIDCustomer-system unique identifier for the person.string
emailEmail address.string
phonePhone number.string
isVIPWhether the fan is in a VIP segment.boolean
firstSeenFirst day the fan was seen on your site or app.string (ISO 8601 date)
lastSeenMost recent day the fan was seen.string (ISO 8601 date)
cityCity (from sync or IP geolocation).string
regionState / province / territory.string
countryCountry name.string
countryCodeISO country code.string
continentCodeContinent code.string
dmaDesignated Market Area.string
timeZoneIANA timezone (e.g. America/Toronto).string
birthdayDate of birth.string (ISO 8601 date)
memberSinceDate the fan became a member.string (ISO 8601 date)
lastTicketScannedAtMost recent moment a ticket attached to this fan was scanned at a venue.string (ISO 8601 datetime)
deviceOSOperating system of the fan's device.string
deviceModelDevice model.string
deviceLanguageDevice language.string
appVersionCustomer app version installed.string
sdkVersionRover SDK version installed.string
notificationAuthorizationPush authorization status reported by the device.string
pushTokenDevice push token.string
tagsTags assigned to the fan.string[]
subscriptionOptInsSubscription types the fan has opted in to.string[]
subscriptionOptOutsSubscription types the fan has opted out from.string[]

Custom trait keys defined by your team appear alongside these. Custom keys must not collide with any system key above.

Computed traits aren't exported

A few traits visible inside the Rover dashboard (age, nextBirthday, tenure, anniversary, atTheVenue) are derived at read time from other stored fields and are not part of the persisted profile, so they do not flow through Data Export. Compute them in your warehouse:

  • age, nextBirthday → from birthday.
  • tenure, anniversary → from memberSince.
  • atTheVenuelastTicketScannedAt within the last 6 hours.

fan_identity_graph

The identity graph is Rover's many-to-one mapping from external identifiers to canonical fan profiles. An external identifier is anything that identifies a person outside Rover. Most commonly that's an email address or phone number, but it can also be a customer-system user ID, mobile device ID, loyalty-program member number, or any custom identifier type your team has configured. One person typically has several identifiers; this dataset records every association.

A record is emitted every time an identifier becomes associated with, modified on, or removed from a fan. The operation field distinguishes those cases:

  • create: this identifier is now associated with this roverID for the first time (or after a previous deletion).
  • update: the identifier's roverID changed. This is what a profile merge looks like in this dataset: when fan A merges into B, every identifier owned by A is transferred in place. The same underlying row stays, with its roverID updated to B. There is no delete for the old (type, identifier, A) triple; the move surfaces only as an update record carrying the new roverID.
  • delete: the association no longer exists. The roverID on a delete record reflects the pre-delete owner: who held the identifier before it was removed.

The natural key for "who currently owns this identifier" is therefore (type, identifier), not (type, identifier, roverID). The same (type, identifier) pair appears in this stream every time its roverID changes (transfers during a merge, deletions and re-adds); the latest record by updatedAt is authoritative.

Identifier types are workspace-defined strings (email, phone, loyaltyMemberId, etc.). There is no fixed enumeration; new types appear as your team configures them.

Watch merges alongside identity changes

fan_profile_merges (below) tracks the underlying roverID survivor mapping. If you also need to know "who currently owns this email" or "which identifiers does this fan currently hold", project a latest-per-(type, identifier) view of fan_identity_graph. Identifier transfers during merges arrive on this dataset directly.

FieldTypeNotes
idstringDeterministic record id.
versionnumber1.
operationstringOne of create, update, delete. Snapshot records are always create; the operation field becomes meaningful in the change stream.
typestringIdentifier type (e.g. email, phone, customer-defined types).
identifierstringThe identifier value itself (e.g. fan@example.com).
roverIDstring (UUID)Canonical Rover ID this identifier maps to.
updatedAtstring (ISO 8601 UTC)When the association was last modified upstream.
emittedAtstring (ISO 8601 UTC)When the record was written into the file.
{
  "id": "a73c1f...",
  "version": 1,
  "operation": "create",
  "type": "email",
  "identifier": "fan@example.com",
  "roverID": "f41657dd-cc7f-40ef-9198-95215791afb1",
  "updatedAt": "2026-05-02T17:15:09.000Z",
  "emittedAt": "2026-05-02T17:15:10.218Z"
}

fan_profile_merges

A profile merge is the moment Rover decides that two existing profiles represent the same real person and consolidates them. This typically happens when an event arrives carrying an identifier (an email, a phone number, a customer ID) that is already associated with a different roverID than the one the event was attributed to. Rover resolves the collision by picking one profile as the canonical survivor and folding the other's identifiers, traits, and history into it. The retired profile ceases to exist as a distinct entity from that point forward.

A record is emitted for every such consolidation. The roverID field holds the retired fan ID (the one that was merged away); the canonicalRoverID field holds the surviving fan ID. The traits payload reflects the surviving profile's trait map immediately after the merge, which is useful if you want to capture the post-merge state without joining back to fan_profiles. traits is null when the surviving profile had no traits to record at the moment of the merge.

After a merge, the retired roverID will not produce any further records in fan_profiles, fan_identity_graph, or fan_track_events. Historical records keyed on the retired ID (a fan_profiles snapshot from yesterday, a track event from last week) remain accurate for the moment they describe, but if you want a unified view of a person's history across the merge boundary you need to apply the remap when joining.

Merges are flat, not chained

When fan A merges into B and then B later merges into C, Rover rewrites every prior merge that pointed at B to point at C. This shows up in the change stream as two records:

  • A new create record: roverID=B, canonicalRoverID=C (the new merge itself).
  • An update record for the original A→B merge: roverID=A, canonicalRoverID=C (the flatten: the existing A row's canonicalRoverID was rewritten to the new survivor).

The same roverID (loser) can therefore appear more than once in fan_profile_merges over time, with canonicalRoverID shifting each time the survivor itself is merged again. The latest record per roverID always carries the current canonical, so you never have to walk a chain to resolve a fan to its final canonical ID.

Identity reassignments emit updates, not deletes

When fans merge, Rover transfers each loser's identifiers to the canonical by updating the existing identity row's roverID in place. In fan_identity_graph you'll see the move surface as an update record carrying the new roverID. There is no corresponding delete for the old (type, identifier, oldRoverID) triple, because the underlying row was not deleted. The natural key for "who currently owns this identifier" is (type, identifier); the latest record by updatedAt for that pair is authoritative.

Merges are not reversible; there is no "unmerge" event. If two profiles were merged in error, the only remediation is to re-import the data and let identity resolution rebuild the graph; this surfaces as new fan profile records, not as a reversal of the original merge record.

FieldTypeNotes
idstringDeterministic record id.
versionnumber1.
roverIDstring (UUID)The fan that was merged away (the loser). The same roverID can appear in subsequent fan_profile_merges records if its canonical is itself merged later (see Merges are flat, not chained).
canonicalRoverIDstring (UUID)The fan that currently survives the merge for this roverID. Always reflects the latest canonical, even after chain flattening.
traitsobject | nullThe traits payload of the canonical fan after the merge, or null if no traits were carried over.
occurredAtstring (ISO 8601 UTC)When the merge happened upstream.
emittedAtstring (ISO 8601 UTC)When the record was written into the file.
{
  "id": "b8f2e1...",
  "version": 1,
  "roverID": "f41657dd-cc7f-40ef-9198-95215791afb1",
  "canonicalRoverID": "9c5b90fc-f930-4332-9ce6-f9952b6cbec8",
  "traits": {
    "email": "fan@example.com",
    "firstName": "Ada"
  },
  "occurredAt": "2026-05-02T17:42:18.000Z",
  "emittedAt": "2026-05-02T17:42:19.103Z"
}

fan_track_events

Track events are behavioral signals about a fan: a mix of device-originated SDK telemetry (app lifecycle, Rover Experience interactions) and server-side message-lifecycle events (a Rover message being sent, opened, or clicked). Unlike the other three datasets, which are change feeds of database state, this stream is event-shaped: every record represents one user-attributable action that happened at the moment given by timestamp.

Each event is linked to a fan profile via roverID, so events can be joined to fan_profiles to enrich them with the user's traits and to fan_identity_graph to associate them with external identifiers.

The set of event names that flow into fan_track_events is an allowlist; anything not on it is dropped at the export boundary. The allowlist is documented in full below.

Track events are append-only. There are no updates, deletes, or backfills of previously-emitted events; an event, once delivered, is final for that point in time.

Track-event envelope

Track events have the standard envelope plus:

FieldTypeNotes
workspaceIDstringFull workspace identifier (organization-...-{uuid}).
namestringOne of the supported names below. Case-sensitive.
timestampstring (ISO 8601 UTC)When the event occurred (on-device for SDK events; server-side moment for message-lifecycle events). Use this for ordering.
attributesobject | undefinedEvent-specific payload. Schema varies per name (see below). Omitted entirely when no attributes are present.

Supported events

The full allowlist as of today:

NameWhen it firesOrigin
App OpenedApp becomes active in the foregroundRover SDK (mobile)
App ClosedApp resigns activeRover SDK (mobile)
App InstalledFirst SDK init after install on this deviceRover SDK (mobile)
Experience Screen ViewedScreen of a Rover modern Experience is presentedRover SDK (mobile)
Experience Button TappedTappable node in a Rover modern Experience is tappedRover SDK (mobile)
Message SentRover records the outcome of sending a message to a fanRover messaging
Message OpenedFan opens (views) a Rover messageRover messaging
Message ClickedFan taps a link inside a Rover messageRover messaging

Each event is documented in detail below.

App Opened

Fires every time the app becomes active in the foreground. Auto-emitted by the SDK; no customer code required.

Attributes: none.

{
  "id": "9f893e451b69b0d1e4d16bc34a8f3fa8",
  "version": 1,
  "roverID": "f41657dd-cc7f-40ef-9198-95215791afb1",
  "workspaceID": "organization-prod-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
  "name": "App Opened",
  "timestamp": "2026-05-02T17:28:41.000Z",
  "emittedAt": "2026-05-02T17:28:42.807Z"
}

Use cases: session counts, DAU/MAU, retention curves, push-open attribution windows.

App Closed

Fires every time the app resigns active (user backgrounded the app, took a phone call, swiped to switch apps, etc). Auto-emitted by the SDK.

Attributes: none.

{
  "id": "...",
  "version": 1,
  "roverID": "f41657dd-...",
  "workspaceID": "organization-prod-...",
  "name": "App Closed",
  "timestamp": "2026-05-02T17:33:11.000Z",
  "emittedAt": "2026-05-02T17:33:12.103Z"
}

Use cases: session-duration analytics (paired with App Opened events on the same roverID), engagement scoring.

App Installed

Fires once per device, the first time the SDK initializes after install (detected by the absence of a stored prior version on-device). Auto-emitted by the SDK.

Attributes: none.

{
  "id": "...",
  "version": 1,
  "roverID": "f41657dd-...",
  "workspaceID": "organization-prod-...",
  "name": "App Installed",
  "timestamp": "2026-05-02T16:00:00.000Z",
  "emittedAt": "2026-05-02T16:00:00.241Z"
}

Use cases: install attribution, cohort-based analysis. Note this is install-time on this device, not first-ever-install for this user; a user reinstalling the app on the same device will fire this event again.

Experience Screen Viewed

Fires every time a screen of a Rover modern Experience is presented to the user. Auto-emitted by the SDK.

Attributes:

PathTypeNotes
attributes.experience.idstringExperience identifier.
attributes.experience.namestringAuthor-supplied experience name. Defaults to "Name" if unset.
attributes.experience.campaignIDstring?Present when launched via a campaign (push, in-app, deep link). Omitted otherwise.
attributes.experience.urlstring?Source URL the experience was loaded from, if any.
attributes.screen.idstringScreen identifier within the experience.
attributes.screen.namestringAuthor-supplied screen name. Defaults to "Name" if unset.
{
  "id": "...",
  "version": 1,
  "roverID": "f41657dd-...",
  "workspaceID": "organization-prod-...",
  "name": "Experience Screen Viewed",
  "timestamp": "2026-05-02T17:42:00.000Z",
  "emittedAt": "2026-05-02T17:42:00.512Z",
  "attributes": {
    "experience": {
      "id": "exp_01HV...",
      "name": "Onboarding Flow",
      "campaignID": "camp_01HW...",
      "url": "https://rover.example.com/exp/01HV..."
    },
    "screen": {
      "id": "scr_01HV...",
      "name": "Welcome"
    }
  }
}

Use cases: funnel analysis (combined with Experience Button Tapped), content-engagement reporting per experience or screen, A/B test exposure measurement.

Experience Button Tapped

Fires when a user taps a tappable node (button, image with action, etc.) in a Rover modern Experience. Auto-emitted by the SDK.

Attributes:

PathTypeNotes
attributes.experience.idstringExperience identifier.
attributes.experience.namestring?Author-supplied experience name. May be omitted (no "Name" fallback for this event).
attributes.experience.campaignIDstring?Present when launched via a campaign.
attributes.experience.urlstring?Source URL of the experience.
attributes.screen.idstringScreen the button lives on.
attributes.screen.namestring?Author-supplied screen name. May be omitted.
attributes.node.idstringIdentifier of the tapped node.
attributes.node.namestring?Author-supplied node name. May be omitted.
{
  "id": "...",
  "version": 1,
  "roverID": "f41657dd-...",
  "workspaceID": "organization-prod-...",
  "name": "Experience Button Tapped",
  "timestamp": "2026-05-02T17:42:14.000Z",
  "emittedAt": "2026-05-02T17:42:14.331Z",
  "attributes": {
    "experience": {
      "id": "exp_01HV...",
      "name": "Onboarding Flow",
      "campaignID": "camp_01HW...",
      "url": "https://rover.example.com/exp/01HV..."
    },
    "screen": {
      "id": "scr_01HV...",
      "name": "Welcome"
    },
    "node": {
      "id": "node_01HV...",
      "name": "Get Started"
    }
  }
}

Use cases: call-to-action conversion rates, A/B test outcome measurement, drop-off analysis between presented screens and tapped buttons.

Message Sent

Fires when Rover records the outcome of sending a message to a fan. One record is emitted per recipient, per message, across all supported channels (push notification, post, SMS, chat, article).

The exact moment "sent" represents depends on the channel: for push notifications it's the response from the push provider; for SMS and chat it's the carrier's delivery callback; for posts it's the moment the post is staged for the recipient. The result attribute distinguishes successful outcomes from failed ones in all cases.

Attributes:

PathTypeNotes
attributes.messageIDstringIdentifier of the message that was sent. Joinable to your Rover messaging tooling, and to subsequent Message Opened / Message Clicked events for the same message.
attributes.resultstringOne of delivered (the channel reported successful delivery to the fan), unreachable (push only: the device's push token was rejected by APNS/FCM as no longer valid, e.g. the app was uninstalled or push permission revoked since the token was registered), or invalid (the message could not be sent: bad payload, expired credentials, channel-specific permanent failure). Fans with no contact identifier for the channel at audience-selection time are filtered out before send and produce no Message Sent record.
attributes.messageTypestring?One of notification, post, sms, chat, article. Omitted if message metadata could not be looked up at emit time.
attributes.messageTitlestring | null?The message title at send time. null for messages that don't carry a title. Omitted if message metadata could not be looked up at emit time.
{
  "id": "...",
  "version": 1,
  "roverID": "f41657dd-...",
  "workspaceID": "organization-prod-...",
  "name": "Message Sent",
  "timestamp": "2026-05-02T17:15:09.000Z",
  "emittedAt": "2026-05-02T17:15:10.218Z",
  "attributes": {
    "messageID": "msg_01HV...",
    "result": "delivered",
    "messageType": "notification",
    "messageTitle": "Welcome to the club!"
  }
}

Use cases: send-volume reporting, deliverability auditing (per-channel result rates), denominators for downstream open and click conversion.

Message Opened

Fires when a fan opens (views) a Rover message, for example expanding a push notification or rendering an inbox post.

Attributes:

PathTypeNotes
attributes.messageIDstringIdentifier of the message that was opened. Joinable to a prior Message Sent for the same messageID.
attributes.messageTypestring?One of notification, post, sms, chat, article. Omitted if message metadata could not be looked up at emit time.
attributes.messageTitlestring | null?The message title at send time. null for messages without a title. Omitted if message metadata could not be looked up at emit time.
{
  "id": "...",
  "version": 1,
  "roverID": "f41657dd-...",
  "workspaceID": "organization-prod-...",
  "name": "Message Opened",
  "timestamp": "2026-05-02T17:18:00.000Z",
  "emittedAt": "2026-05-02T17:18:00.512Z",
  "attributes": {
    "messageID": "msg_01HV...",
    "messageType": "notification",
    "messageTitle": "Welcome to the club!"
  }
}

Use cases: open-rate funnels, engagement scoring on messaging campaigns, time-to-open distributions when paired with Message Sent.

Message Clicked

Fires when a fan taps a link inside a Rover message.

Attributes:

PathTypeNotes
attributes.messageIDstringIdentifier of the message that contained the link.
attributes.linkstringThe link target as it was tapped.
attributes.messageTypestring?One of notification, post, sms, chat, article. Omitted if message metadata could not be looked up at emit time.
attributes.messageTitlestring | null?The message title at send time. null for messages without a title. Omitted if message metadata could not be looked up at emit time.
{
  "id": "...",
  "version": 1,
  "roverID": "f41657dd-...",
  "workspaceID": "organization-prod-...",
  "name": "Message Clicked",
  "timestamp": "2026-05-02T17:18:42.000Z",
  "emittedAt": "2026-05-02T17:18:42.331Z",
  "attributes": {
    "messageID": "msg_01HV...",
    "link": "https://example.com/promo/spring",
    "messageType": "notification",
    "messageTitle": "Welcome to the club!"
  }
}

Use cases: CTA conversion rates per message, link-attribution analysis, downstream behavior or revenue attribution to a specific message.

Only the events above flow into fan_track_events. Other SDK or internal events (geofence and beacon transitions, push lifecycle hooks, classic-experience events, and customer-defined names) are not part of this dataset.

Previous
File Format & Layout