Skip to content

Event design

Last updated

Whisperr predicts churn from the events your app and backend send. This page helps you choose those events, name them, and send them so that Whisperr puts each one on the right user’s timeline exactly once.

Send the events that tell you whether a user will stay. Many of them happen on your backend:

SignalExamples
Payment healthpayment_failed, card_expiring, invoice_overdue
Lifecycletrial_started, trial_expired, subscription_cancelled, plan_downgraded
Engagementreport_generated, project_created, checkout_completed
Frictionexport_failed, support_ticket_opened

Whisperr stores every valid event, but only registered events drive decisions:

  • Before an event list is saved for your app, Whisperr registers each new event name that it receives from an SDK or the ingestion API, up to 50 names, so it can build your plan from the events your app really sends. Review the list in the dashboard before you build the plan.
  • After an event list is saved, by you in the dashboard, by your coding agent or by the pull request agent, a new name is stored and listed on the Data page, but it drives no decisions until you add it to the list.

The mobile SDKs also send reserved automatic events: app_installed, app_updated, app_opened, app_backgrounded, app_error, app_crashed, screen_viewed, push_opened, push_received and push_permission_changed. Whisperr knows these names for every app. Do not send them with track() yourself; that counts them twice.

From React Native 0.7.0 (with @whisperr/expo 0.2.0), Flutter 0.8.0 and Swift 0.7.0, the mobile SDKs send these signals with no code from you. They tell Whisperr how a user came into the app, how much each visit did, and whether the app failed them.

SignalWhere it goes
Sessioncontext.session_id on every event (sess_ plus a UUID). A new session starts on each cold start and after 30 minutes with no event.
Launch sourceapp_opened.launch_source: push, shortcut, widget, email, universal_link, deep_link or direct. A link adds link_route (the path only, with ids replaced by :id), link_host for http(s) links, and utm_source, utm_medium and utm_campaign when the link has them. A push or email open adds whisperr_message_id.
Visit depthapp_backgrounded adds session_id, screen_count, unique_screen_count, event_count (your track() events) and error_count for the visit that ends.
Errorsapp_error for an uncaught error and for each trackError() call: error_kind, fatal, error_name (the type, such as TypeError), fingerprint, screen_name. Never the message, the stack or a URL. At most one per fingerprint and 20 per session.
Crashesapp_crashed on the next launch, before its app_opened, when the last run ended while the app was in the foreground. Debug builds never send it.

To mark a widget or a quick action, add wsrc=widget or wsrc=shortcut to the link it opens. Screen counts need screen_viewed events: use the screen helper of your SDK (React Native, Expo, Flutter) or call screen().

captureErrors: false turns off the automatic error capture. trackError() still sends. Turning off all automatic events also turns off app_error capture and app_crashed. Errors and crashes are diagnostics data: see what the SDKs send before you update your App Store and Google Play answers.

The server SDKs (Node, Python, PHP, .NET) send no session, launch or error signals, and have no trackError(): a server error is not what the user saw. When a failure reaches the user, such as a declined payment or a failed export, send it as your own event from your backend (payment_failed, export_failed).

Do not send personal data in event properties. Whisperr drops property keys that name personal data or secrets, such as email, phone, address, name or password. See properties.

Name events in lowercase snake_case that matches ^[a-z0-9]+(?:_[a-z0-9]+)*$. Start with a letter and keep it to 64 characters: Whisperr registers only such names. The Whisperr API trims the name and changes it to lower case before it checks it, and rejects anything else. Every SDK checks the name before it queues the event, so one bad name cannot cost you a batch.

Use object, then verb, in the past tense: payment_failed, plan_upgraded, subscription_cancelled.

Every event carries an external_user_id: your stable user id, the same one you use in your own database. Server SDKs take it on every call. The browser and mobile SDKs fill it in after identify().

Events from before login are not lost. A client SDK either holds them until identify(), or sends them under an anonymous_id that the next identify call links to the user. Use the same id everywhere, and events from your app and your backend merge into one timeline.

Whisperr never messages a user it knows only by anonymous_id.

Events carry occurred_at, captured when the event happens, not when it is sent. Events recorded offline or held in a queue keep their real time. Whisperr accepts times from 30 days in the past to 5 minutes in the future. The SDKs send UTC with millisecond precision (2026-06-14T12:00:00.000Z). Without occurred_at, Whisperr uses the time it accepted the request.

Delivery is at-least-once: a timeout can mean the server got the batch and the response was lost, so the SDKs retry. Every event carries an idempotency key, $message_id in context, that stays the same on every retry. Whisperr stores an event once per $message_id, so retries never create duplicates.

If you call the API directly, generate $message_id once per event and keep it across retries.

Common questions

Do I have to register an event before I send it to Whisperr?

No. Whisperr stores every valid event. Until an event list is saved for your app, Whisperr also registers each new event name that your app or backend sends with an SDK or the ingestion API, up to 50 names, so it can build your plan from them. Once a list is saved, or after those 50 names, a new name is stored and listed on the Data page, but it drives no decisions until you add it to the list.

Which events do the Whisperr SDKs send on their own?

The mobile SDKs send reserved automatic events such as app_installed, app_opened, app_backgrounded, app_error, app_crashed, screen_viewed and the push events. Whisperr knows these names for every app, so you do not register them.

Can I send the same event from my app and my backend?

Yes, if both use the same external_user_id. Give each event its own $message_id. Two events with different message ids are two events, even when everything else matches.