You already have analytics. You still don't know where onboarding breaks.
Permalink to You already have analytics. You still don't know where onboarding breaks.You have an onboarding flow and you have a general-purpose analytics SDK. Between them you can tell roughly how many people finish, as long as somebody remembered to fire the right event on the right screen, and nobody has renamed one since. Observe is that, without the maintenance. You name your onboarding screens once, in order, and every impression, exit, dwell time and paywall view lands against that list. Drop-off is reported per screen, not per event name someone invented in a hurry.
Your onboarding stays exactly as you built it. Observe adds one dependency and reads it.
What comes back
Permalink to What comes back- Sessions and drop-off at every screen, in your declared order.
- Median dwell per screen, timed by the SDK, not by you.
- Completion rate, paywall reach rate, and trial start rate.
- The same reads across app versions, so a release is a cohort.
- A trust verdict on the pipeline itself. An app with no events reads
no_data, andno_datais never rendered as green.
That last one is the difference from an event bus. A general analytics SDK will keep showing you a funnel after the events stop arriving, and the funnel will look like a drop in conversion.
The whole integration
Permalink to The whole integrationThree calls. Everything else on the surface is optional.
import WeirObserve
public enum Weir {
public static func observe(
appId: String, writeToken: String, endpoint: URL,
flow: String = "onboarding",
screens: [String],
userId: String? = nil,
flushInterval: TimeInterval = 30,
sessionTimeout: TimeInterval = 1800 // 30 min backgrounded → abandon
)
public static func screen(_ name: String, index: Int? = nil,
properties: [String: JSONValue] = [:])
public static func completed(properties: [String: JSONValue] = [:])
public static func paywallShown(_ paywallId: String? = nil,
properties: [String: JSONValue] = [:])
public static func purchaseIntent(product: String,
properties: [String: JSONValue] = [:])
public static func purchaseResult(product: String, outcome: PurchaseOutcome,
error: String? = nil)
public static func track(_ name: String, _ properties: [String: JSONValue] = [:])
public static func setUserId(_ id: String?)
public static func flush()
}Call observe once at launch, screen once per screen, completed where onboarding ends. Android and React Native expose the same shape. The step-by-step version is the Observe quickstart.
The screen order is declared once and never passed per call. A hand-typed index is a second source of truth that drifts from the real order, and the funnel is ordered by that index. A screen name that is not in the declared array is recorded as undeclared and turns the app amber. It is never absorbed into the funnel silently.
Two claims, and how to check them
Permalink to Two claims, and how to check themBoth are internal verification runs on a Mac mini, against native Release builds. They are not customer results. Weir is a controlled private alpha — see Evidence status for the full boundary.
About fifteen minutes to integrate
Permalink to About fifteen minutes to integrateThe bar was fifteen agent minutes. Three consecutive cold runs came in at 86, 56 and 67 seconds. Each run gave a fresh agent nothing but the public documentation and a sample app with no Weir code in it. The run sandbox denies this repository, so an agent that consults the source fails the run outright. Each agent added the calls itself, produced an iOS Release build, drove five onboarding screens under Maestro, and rendered a funnel in the dashboard.
Retained at demo/runs/rfc015-observe/rfc015-observe-20260901-010540Z-89978/ and its two siblings under demo/runs/rfc015-observe/. Each summary ends RFC-015 COLD OBSERVE PASS 9/9.
The numbers are correct, and the check is not ours to grade
Permalink to The numbers are correct, and the check is not ours to gradeA Release build was driven through 30 scripted device sessions: 12 completions, 10 abandons on screen 3, 5 on screen 4, 3 reaching the paywall. The runner computed ground truth. The dashboard was then read in a real browser and compared against it.
| Read | Ground truth | Rendered |
|---|---|---|
| Sessions per step | 30, 30, 30, 20, 15 | 30, 30, 30, 20, 15 |
| Drop-off per step | 0, 0, 0, 0.333, 0.25 | 0, 0, 0, 0.333, 0.25 |
| Completion rate | 0.4 | 40.0% |
| Paywall reach | 0.5 | 50.0% |
| Trial start | 0.1 | 10.0% |
Device dwell medians landed within 33 ms of the scripted sleeps, against a tolerance of 150 ms. Retained at demo/runs/rfc015-observe/rfc015-observe-dashboard-funnel-20260903-052012Z/, with the source device run at docs/verification/rfc015-observe-device-funnel-2026-09-01.txt.
Three failure modes were induced rather than asserted:
- A misspelled screen name. A build emitted
experiancewhile declaringexperience. One impression in 20 was flagged undeclared, trust went amber, and the dashboard labelled the stepUndeclaredinstead of reordering the funnel. (demo/runs/rfc015-observe/rfc015-observe-device-undeclared-20260903-050500Z/) - A kill mid-flow and a 609-second network blackhole. One session survived across two launches with contiguous sequence numbers and no duplicates. On recovery the queue drained to zero with no eviction: 18 events enqueued, 18 uploaded. (
demo/runs/rfc015-observe/rfc015-observe-loss-dedupe-20260903-045133Z/) - An app with no data at all. A registered Observe app with an empty events table reported
no_dataon the home read, the Overview, and the funnel page. It never reported green. (demo/runs/rfc015-observe/rfc015-observe-configless-20260903-045500Z/)
What Observe does not do
Permalink to What Observe does not do- It does not rewrite your onboarding. No flow spec, no served config, no screens of ours in your app.
WeirObservedepends onWeirCoreonly and never links the UI target. - It does not open a WebView. There is no web runtime anywhere in the integration.
- It does not fetch configuration or ship anything over the air. Serving is a separate opt-in product, and adopting Observe does not adopt it.
- It does not run experiments. No variant assignment on an Observe app until you ask for it.
- It does not replace your analytics. Observe measures one flow. Keep your event bus for everything after onboarding.
- It does not claim revenue.
purchase_resultis a funnel marker, never a financial source of truth.userIdis an opaque string you supply; the SDK invents no identifiers.
Next
Permalink to Next- Observe quickstart — the iOS, Android, and React Native integration, step by step.
- Evidence status — what Weir does and does not claim today.
- Monitoring — the funnel and health reads an Observe integration watches.