OrbitCapture sends are what every number here is computed from, under the same capture policy and the same privacy floor.
What is different about it
Every analytics product an iOS team is offered was built for the web and adapted. This one starts from the app.- Sessions are lifecycle sessions, not 30 minute timeouts. A session starts when the app comes to the foreground and ends when it goes to the background and stays there. Session length is foreground time only, so a phone left in a pocket does not become engagement.
- The app version is a first-class dimension, not a custom property. The header carries a version selector and the Versions tab is a table of versions.
- No IDFA, ever, and no App Tracking Transparency prompt.
Identities: how a person is counted
Set in Settings, then Analytics. It changes what every number means for everyone in the workspace, so writing it is admin or owner.
Anonymous mode means no day can be joined to another, so retention answers D0 and D1 only, which the console says out loud. Saving it writes into the capture policy, so the change appears in Settings, Telemetry’s change log with a version of its own and every device picks it up on its next poll.
The policy poll and an upload request still carry the real installation id in their header, even in anonymous mode. That is how the console asks a specific phone for its buffer, and it is not an analytics record.
Lifecycle states
Where everyone stands, one state each, evaluated nightly. The first rule that matches wins, so paying beats activated and churned beats at risk.
Every state on the Overview lifecycle bar is a link into People filtered to it.
The tabs
- Overview: active now (read live, not from a rollup, because a five minute question answered by a nightly job would be about last night), eight usage tiles with sparklines, retention D1, D7 and D30, the lifecycle bar, what changed (the metrics whose move is larger than two standard deviations of their own recent daily variation, rather than a fixed percentage that is loud on a small fleet and silent on a large one), and the top screens and events.
- Versions: the adoption curve, and a table of every version with sessions, adoption, crash free sessions, hang rate, D1 and D7 retention, revenue per active and store rating, optionally with a delta column against the version below.
- People: everybody the project has seen, in their lifecycle state, with segments. Pro and up.
- Behaviour: events, screens, paths, funnels, and rage and dead taps. Pro and up.
- Revenue: the money tab, below. Pro+ and up.
- Reports: the digest, saved queries, export and Ask analytics. Pro+ and up.
Revenue: three sources that see different things
Money is append-only and written by sources that cannot see the same things. Whichever arrives first writes the row; the others append themselves and fill in only what they alone know.
A fourth door,
POST /v1/ingest/revenue, takes revenue Orbit cannot read from the App Store at all: a web checkout, an invoice, a code redeemed off platform.
The three are not meant to match exactly, and the reconciliation card is where a gap that explains itself is told from one that does not. Raw transaction ids never enter a column: only sha256 of them does, and the three sources converge on the same hashes.
Setting up the notification endpoint
1
Copy the URL
Settings, then Analytics. The card carries the URL to paste, as both the production and the sandbox URL.
2
Paste it into App Store Connect
General, then App Information, then App Store Server Notifications.
3
Send Apple's test notification
The card shows the last notification’s type, so “TEST” arriving is the end-to-end proof that the URL works.
The App Store Connect key, and the role it needs
The same key that reads Release and pulls TestFlight crashes also runs the nightly analytics reports sync. The role it needs here is not the role Release needs, and that is the usual reason the App Store block says nothing arrived.
A key with only Developer or App Manager reads Release perfectly and gets a 403 here, so the two syncs record their outcomes separately and the card shows Apple’s own message.
Two dates are Apple’s schedule, not a delay Orbit adds: the first report of a new request appears 24 to 48 hours after it is created, and a day is only final two days after it ends. The newest App Store day is always a couple of days behind today.
Reports
- The weekly digest goes out Monday 09:00 in the project’s own time zone, on the alert channels the project already has. Choose the zone and tick the channels on the Reports tab, beside a preview built from the same fold the Monday job runs, so you can read what you would get before choosing a channel. Nothing is sent until at least one channel is ticked. The week identifies a digest, never the minute it was sent, so an hour when Orbit was down costs freshness and never the week.
- Saved queries are one metric, one window and one optional breakdown and filter: exactly what the console reads, the CLI reads with
orbit analytics query, and an Ask analytics answer writes down. Pro+ keeps 25; Ultra is unlimited. - Export writes any table on the page as CSV, over the window and unit the page is reading, with a day the rollup has not written as an empty cell rather than a zero. Revenue is the one table with a gate: a month is exported only once it is settled, meaning the month is over, Apple’s reports for its last days are final, the nightly reconcile has looked at it since, and it found no transaction one source has and another does not. A month left out never becomes a silent zero: the file says which month and what is missing.