Event Tracking Across Android, iOS, and Web: One Contract
A registration report can look consistent until someone separates Android, iOS, and Web. The same label might represent a button tap on one platform, a submitted request on another, and a successfully created account on the third. Combining those records produces a number, but not necessarily an answer to the product team's question.
Cross-platform event tracking starts with one business contract, not identical SDK syntax. Define the event's meaning, authoritative trigger, identity, required properties, and time basis; then implement that contract through each platform's appropriate collection library. Verify the retained records and the resulting metric separately from whether a request was accepted.
This is an original teaching guide with fictional identifiers and synthetic fixtures. I work on SensorFlow, a self-hosted event analytics project; AI helped draft the guide, and its references, code and product boundaries were checked before publication. The example is a local contract test, not a benchmark, customer case study, or proof that every SDK version works with SensorFlow.
What should remain the same across platforms?
A shared event contract keeps business meaning consistent while allowing platform-specific implementation. The event name, trigger, identity policy, property types, occurrence-time unit, and environment rule should be explicit. SDK initialization, lifecycle handling, native permissions, transport behavior and optional plugins need their own platform-specific validation.
Consider a fictional event called signup_submitted. Its trigger is a valid registration request entering the application workflow, not any tap on a button bearing a signup label. It carries the selected signup method, a non-sensitive test identity, the client platform, the application release and the contract version.
That definition lets the mobile and Web teams reason about the same action without pretending their interfaces are identical. A native screen transition, a Web route change and an embedded browser interaction can differ while still participating in one well-defined business journey. The contract describes the meaningful boundary rather than every interface detail.
Do not force every platform to emit fields that have no valid interpretation there. A property needed only for one platform can be optional with a documented purpose. An empty placeholder is not automatically better than an absent field, and inventing an identity simply to satisfy a schema can damage subsequent analysis.
Use an SDK ecosystem, not a universal syntax claim
Sensors Data publishes separate collection repositories for Android, iOS, and Web JavaScript. Those repositories are starting points for their respective integration instructions. Their existence does not prove that every optional feature is interchangeable across platforms or destinations.
Your integration checklist should name the exact platform and SDK version being tested. Record how the SDK is initialized, which optional modules are enabled, and what transport or encryption configuration applies. A generic statement that an application supports mobile and Web is not enough to reproduce a failed request.
If you use a wrapper framework, record the wrapper and underlying native integration as separate dependencies. A problem can occur in the application call, the bridge, the native library, the transport, or the receiver. Treating the entire path as one SDK makes it harder to identify where the intended event changed.
Likewise, a server SDK is not simply a fourth frontend. Backend events may know a committed operation and authenticated account that the client cannot yet observe. Use that advantage to define authoritative outcomes rather than duplicating the client's interface events with different property names.
Choose a business question before collecting events
Write one question the team can calculate. For example: among synthetic visitors who opened registration, how many submitted a valid form and then obtained a confirmed account within the declared observation window? Decide whether the analysis entity is a visitor, account, workspace, session, or operation.
The question determines which events are necessary. A view, a valid submission and a confirmed creation represent different stages. Tracking more clicks will not resolve a disagreement about which stage counts as success; the missing ingredient is an agreed definition, not a larger event dictionary.
For an activation question, account creation might only be the entry event. The outcome could be the first completed setup or another meaningful action agreed by the product team. Define that outcome independently instead of relabeling registration success as activation because it is easier to collect.
Keep the question and contract available to the dashboard author. Engineering can emit correctly shaped data while a report counts the wrong population or applies a different window. The people implementing instrumentation and the people interpreting results need the same written specification.
Separate client intent from confirmed outcomes
A client can describe a tap, a form validation result or an attempted submission. The application backend can describe whether the corresponding account transaction committed. These observations may both be useful, but they should not share a success label merely because they belong to the same interface flow.
Use an ownership rule for every important outcome. If account_created represents a committed backend state, a frontend success animation is not its authoritative source. A network failure after a successful transaction also needs a defined recovery behavior; otherwise the client can report failure while the account already exists.
For a payment flow, distinguish initiation, confirmation, rejection and refund. A callback retry should not manufacture a second purchase outcome. Whether two records represent one business operation depends on your application contract and reconciliation rules, not only on their event names or delivery times.
You can retain both diagnostic and analytical events if their meanings are clear. The mistake is combining different observations under one vague label and expecting the analytics interface to infer the correct business state. Explicit names and source ownership make both debugging and reporting more interpretable.
Define identity before interpreting a shared funnel
Before signup, an application often has an anonymous identifier; afterward it may have an account identifier. Write down how those identities are connected and which entity the report counts. Similar-looking strings in two platforms are not evidence that they refer to the same person or workspace.
Test anonymous browsing followed by successful signup, an existing account signing in, and logout followed by another synthetic account on the same device. Include an account used on more than one platform when that is part of the product's expected behavior. Inspect the destination's actual identity behavior rather than assuming a login method produces a universal merge policy.
Avoid using a real email address or phone number as a convenient QA identity. Give synthetic identifiers a test namespace and keep them distinguishable from production users. Restrict sensitive properties and request contents; an event pipeline should not become a second uncontrolled copy of registration forms.
Do not “fix” a broken funnel by joining every record sharing an unreliable device attribute. A shared device, a browser reset or a workspace with multiple members can invalidate that shortcut. Resolve identity according to the business entity and supported implementation, with documented limitations.
Keep property types and units deliberate
Choose types once and test them on each platform. A quantity sent as a number on Android and a string on Web can be accepted by some receiving systems while still producing inconsistent filters or aggregations. Document whether an optional property is absent, null, empty, or a meaningful value.
Money needs a representation and a currency, not just a field named amount. Time needs an occurrence-time unit and a timezone policy, not just a field named time. Durations, counts and identifiers should also have explicit meanings; a numeric-looking identifier is not necessarily a quantity that should be summed.
Keep release metadata separate from the contract version. One release may change the interface without changing event semantics; another may change a trigger and require a new analytical interpretation. Reporting both as the same change prevents the team from explaining why counts shifted after deployment.
The example below describes normalized teaching records. It is not the raw Sensors Data SDK wire format and should not be pasted into a real receiver as if it were one. The normalization step itself needs to be implemented and verified in whichever pipeline you operate.
Run a small cross-platform contract check
Save this as event-contract.mjs and run node event-contract.mjs with Node.js 18 or newer. It needs no packages, database, account, token, or network access. The sample validates three fictional records and rejects five deliberately invalid variants.
import assert from 'node:assert/strict';
const allowed = new Set(['android', 'ios', 'web']);
function valid(row) {
return row.event === 'signup_submitted' &&
typeof row.user === 'string' && row.user.startsWith('qa-') &&
row.contractVersion === 1 && row.environment === 'test' &&
allowed.has(row.platform) &&
Number.isSafeInteger(row.occurredAtMs) &&
row.occurredAtMs >= 1_000_000_000_000 &&
row.occurredAtMs < 10_000_000_000_000 &&
['email', 'sso'].includes(row.method) &&
typeof row.appVersion === 'string' && row.appVersion.length > 0;
}
const base = {
event: 'signup_submitted', user: 'qa-visitor-a',
contractVersion: 1, environment: 'test',
occurredAtMs: 1_790_000_000_000, method: 'email', appVersion: 'qa-build'
};
const fixtures = [...allowed].map(platform => ({ ...base, platform }));
for (const row of fixtures) assert.equal(valid(row), true);
const invalid = [
{ ...fixtures[0], occurredAtMs: 1_790_000_000 },
{ ...fixtures[0], occurredAtMs: '1790000000000' },
{ ...fixtures[0], environment: 'production' },
{ ...fixtures[0], platform: 'unreviewed-platform' },
{ ...fixtures[0], method: 'free-form-value' }
];
for (const row of invalid) assert.equal(valid(row), false);
console.log('3 platform fixtures accepted; 5 invalid fixtures rejected.');
The timestamp bounds are a deliberately narrow teaching guard for millisecond-shaped contemporary timestamps. They are not a universal date-validation policy. Replace them with an explicit allowed time range appropriate to your test when validating older data or a production application.
The check does not verify that an SDK emitted an event at the correct business trigger. It does not initialize any Android, iOS or Web SDK, validate wire-level compatibility, test identity merging, or prove successful ingestion. A passing local fixture is one acceptance layer, not permission to skip device and receiver tests.
Repeat the contract on real test clients
Next, implement the contract with the selected SDK versions in a test application. Run one normal signup journey on each required client platform. Observe the trigger, sanitized outgoing request, receiving response, retained event, and expected report result using non-sensitive synthetic identities.
Record platform, application release, SDK version, enabled modules and destination configuration with the test result. A screenshot showing an event name is useful but insufficient if it omits the version or trigger. Another developer should be able to repeat the test without knowing an undocumented sequence of manual steps.
Then add failure cases. Try an invalid form, rejected registration, interrupted connection, repeated submission and delayed delivery. State which conditions are controlled in the test and which are only observed; do not claim broad reliability from a single successful client request.
For an existing production integration, keep this test separate from the live reporting population. A test marker should remain available through the path so that QA traffic can be excluded consistently. Filtering only at the final dashboard does not undo inappropriate collection of sensitive data earlier in the pipeline.
Compare metric definitions, not just stage labels
Suppose every platform sends the expected event names, but one report counts independent stage users and another counts ordered completed journeys. Those reports can disagree even when the pipeline retains every record. Before debugging delivery, align identity, entry rules, event order, filters and completion windows.
The official Sensors Data funnel guide describes distinct counting modes and calculation rules. That is a useful reminder that “funnel” is not one interchangeable computation. Read the settings of the tool you actually use instead of importing an assumed default from another interface.
Preserve a small fixture with a success, an abandonment, an outcome without entry, a retry and an outcome outside the observation window. Write the expected result beside those records. The fixture should expose the rule being tested rather than making every synthetic user complete every step.
Do not use the three schema-validation fixtures above as proof of funnel correctness. They test a record contract only. Journey-level acceptance needs a separate sequence, population and expected metric; this separation makes the evidence more useful when a later release changes either collection or reporting.
Three scenarios where this method helps
For a small SaaS team, choose one registration or activation question before instrumenting every interface element. Give product, client engineering and backend engineering the same trigger definitions. The concrete deliverable is a shared contract plus expected test journeys, not a promised conversion improvement.
For a commerce team, let client interaction events explain intent and let confirmed business events explain payment state. Include a callback retry and refund in the acceptance sample. The goal is to distinguish delivery repetition from business repetition before making a purchase-count chart.
For a team migrating an existing event destination, keep the old contract visible while testing the new path. Compare property types, identity transitions, timestamps and a controlled analytical result, not only raw totals. Retain the ability to reverse the destination change until the agreed acceptance criteria are met.
All three teams may use different software. The reusable part is the evidence boundary: the same business question, explicit trigger ownership, representative records and reproducible acceptance. A new tool cannot supply meaning that the team never specified.
Where SensorFlow fits, and what remains to verify
SensorFlow's public repository describes a self-hosted event analytics path using existing official Sensors Data SDK instrumentation. The open-source deployment starts with synthetic demo data; real SDK ingestion requires separate license activation. SensorFlow is independent of Sensors Data and is not an equivalent implementation of every feature in a mature analytics suite.
The relevant architecture is collection SDK, receiving service, retained event data and analysis. SensorFlow uses Go, ClickHouse and Apache Superset for that path, but choosing components is not the central task in this guide. The contract and its acceptance evidence remain necessary whichever destination you operate.
For a concrete deployment path, the official Quick Start distinguishes demo inspection from real ingestion. Use it to understand the stages; do not interpret a demo chart as validation of your real client versions. Optional SDK plugins and identity behavior still require explicit checks.
The project's SDK migration guide provides a way to discuss migration boundaries, while its event tracking introduction explains the basic event model. These are supporting materials, not certifications that every platform, version and configuration has been tested.
Frequently asked questions
Does one event contract mean one SDK for every language?
No. The contract describes shared business meaning, whereas SDK integration follows each platform's actual library and instructions. Record the chosen versions and configuration; do not turn the existence of several collection libraries into a claim of universal compatibility with a receiver.
Should every platform emit the success event?
Only when that platform is the agreed authoritative source for the business state. If the backend owns account creation or payment confirmation, client events should describe their own observations. Duplicating success without a reconciliation rule can change operation counts.
Can the local script prove a migration is ready?
No. It checks normalized teaching records and some invalid values without contacting a real service. A migration also needs real client tests, receiver and retained-record evidence, analytical equivalence for the required reports, and a rollback plan.
Is an accepted HTTP request enough to trust analytics?
No. Separate event emission, transport, retention and metric calculation. A response tells you something about one request; it does not establish correct business meaning, complete delivery, identity linkage or a reproducible conversion result.
A release checklist worth keeping
Before releasing cross-platform instrumentation, agree on the question, source ownership, identity entity, property types and time basis. Run representative success and failure journeys on the actual selected SDK versions. Keep sanitized evidence and the expected analytical result with the application and contract versions.
Then make changes small enough to investigate. If one platform's result differs, the contract and fixtures should help locate the boundary rather than encourage another broad instrumentation rewrite. Begin with one event whose meaning and retained data you can explain; that is more useful than a dashboard full of ambiguously comparable numbers.
评论
发表评论