whisperr 0.3.5
whisperr: ^0.3.5 copied to clipboard
Official Whisperr SDK for Flutter — identify users and track product events to power churn-prevention interventions.
Whisperr SDK for Flutter #
Identify your users and track product events so Whisperr can decide and deliver churn-prevention interventions. Two calls do the work: identify() and track().
Install #
dependencies:
whisperr: ^0.3.0
Initialize #
Call once at startup (e.g. in main). Get an app ingestion key from the Whisperr dashboard → Developer → API Keys.
import 'package:whisperr/whisperr.dart';
await Whisperr.initialize(apiKey: 'wrk_xxx');
baseUrl defaults to https://api.whisperr.net; pass it only to target a self-hosted or local backend.
Identify #
Set who the current user is. Idempotent and safe to call on every login. Traits are merged server-side; channels are how Whisperr can reach the user (and whether it's allowed to).
// Common case — email/phone/pushToken expand into opted-in channels:
await Whisperr.instance.identify(
'user_123',
email: 'ada@example.com',
phone: '+15551234567',
pushToken: fcmToken, // expands to an opted-in push channel
traits: {'name': 'Ada', 'plan': 'pro'},
);
// Full control — consent and verification:
await Whisperr.instance.identify(
'user_123',
channels: [
WhisperrChannel.email('ada@example.com', verified: true),
WhisperrChannel.sms('+15551234567', optedIn: false), // opted out of SMS
],
);
Whisperr decides which channel to actually use based on engagement — there's no "preferred channel" to set. Express an explicit user choice via
optedIn: falseon the channels they don't want.
identify() also sends traits['locale'] (BCP 47, from the platform locale) and traits['timezone_offset_minutes'] (the device's current UTC offset — Flutter can't obtain an IANA zone name without a plugin) by default; pass your own traits['timezone'] (an IANA name such as Europe/Berlin) or traits['locale'] to override, and nothing is sent for a value the platform can't provide.
Push notifications #
The SDK never bundles a push library — hand it the token your own messaging
setup produces (e.g. firebase_messaging) and Whisperr keeps the push
channel current:
import 'package:firebase_messaging/firebase_messaging.dart';
final messaging = FirebaseMessaging.instance;
// Current token (safe on every launch — repeats are a no-op):
final token = await messaging.getToken();
if (token != null) await Whisperr.instance.setPushToken(token);
// Rotations, forwarded automatically:
final sub = Whisperr.instance.attachPushTokenStream(messaging.onTokenRefresh);
- Called after login,
setPushTokenre-identifies the push channel immediately. - Called before login, the token is buffered and attached to the next
identify(). - Repeats are deduped across restarts: the last-sent (user, token) pair is
persisted alongside the queue, so calling
getToken()+setPushTokenon every launch never re-sends an identify for an unchanged token. - Token rotation is handled: the previously sent token is opted out and the new one opted in, so stale tokens don't accumulate — and tokens from the user's other devices are never touched.
- After
reset()(logout), callsetPushTokenagain once the next user logs in.
Track #
Record product events. Buffered and sent in batches; the timestamp is captured at call time, so events recorded offline keep their real time.
Whisperr.instance.track('checkout_completed', properties: {'amount': 42, 'currency': 'USD'});
Event names must be
snake_case. Only events that map to the events you configured during onboarding drive interventions; others are accepted but inert.
Logout #
await Whisperr.instance.reset(flushBeforeReset: false); // clears identity locally; drains in background
How delivery works #
- Durable queue —
identifyandtrackare appended to an ordered queue and delivered in order.identifycalls hitPOST /v1/identify;trackcalls are coalesced intoPOST /v1/events/batch. - Batching — flushes on an interval (
flushInterval), when the buffer hitsflushAt, on app pause/detach, or when you callflush(). - Offline — the queue is persisted (via
shared_preferences) and survives app restarts. Transient failures (network, 429, 5xx) retry with exponential backoff; auth errors (401/403) pause delivery and keep the queue; permanent client errors (4xx) drop the offending item so the queue keeps moving.
Options #
await Whisperr.initialize(
apiKey: 'wrk_xxx',
options: const WhisperrOptions(
flushInterval: Duration(seconds: 15),
flushAt: 20,
maxBatchSize: 500, // backend hard cap
maxQueueSize: 1000, // drops oldest beyond this
enablePersistence: true,
debug: false,
),
);
await Whisperr.instance.flush(); // force delivery (e.g. before a critical await)
A note on the API key #
The ingestion key is embedded in your app, like a Segment write key or Amplitude API key. It can only ingest events for your app; treat it as publishable, not secret.
Durable channel changes #
For logout/token revocations that your application journals, use
identify(userId, channels: [...], requirePersistence: true). Clear the journal
only after the call succeeds. A successful call confirms that the configured
persistence implementation saved the queue; it does not confirm network delivery
or server acceptance. Disabled persistence, storage failure, and a queue filled
with identify operations reject the call so the application can retain its journal
and retry. Invalid server requests can still be rejected permanently.
Queue capacity remains bounded. Telemetry events may be evicted; pending identify
operations are protected. Normal identify() calls also reject when all queue
slots contain identifies. Token stream forwarding catches and reports these errors.