Skip to content

Event design

Whisperr is not a general analytics warehouse — it’s a churn engine. The events worth sending are the ones that predict whether a user stays.

High-signal churn events, most of which live on your backend:

Signal Examples
Payment health payment_failed, card_expiring, invoice_overdue
Lifecycle trial_started, trial_expired, subscription_cancelled, plan_downgraded
Engagement report_generated, project_created, checkout_completed
Friction export_failed, support_ticket_opened

Only events that map to the events you configured during onboarding drive interventions — others are accepted but inert, so you can instrument broadly and wire things up later.

Event names must be lowercase snake_case, matching ^[a-z0-9]+(?:_[a-z0-9]+)*$. The server rejects anything else, and every SDK validates before enqueueing so one bad name can’t poison a batch.

Prefer object_verb past tense: payment_failed, plan_upgraded, subscription_cancelled.

Every event carries an external_user_idyour stable user id, the same one you use in your own database. Server SDKs always take it explicitly; the browser and mobile SDKs fill it in from identify() and retroactively attribute events buffered before login. Use the same id everywhere and frontend + backend events merge into a single timeline with no extra work.

Events carry occurred_at — captured at call time, not send time, so events recorded offline or buffered keep their real time. Accepted range is +5 minutes to −30 days from now, RFC3339 UTC with millisecond precision (2026-06-14T12:00:00.000Z). The SDKs handle this for you.

Delivery is at-least-once: a timeout can mean the server got the batch and the response was lost, so SDKs retry. Every event carries a per-event idempotency key — $message_id in context — that stays stable across retries, and the server deduplicates on it. You never see double events.

If you call the API directly, generating and preserving $message_id is your responsibility.