Python SDK
Last updated
The whisperr package sends events and identify calls from a Python
backend. Use it for facts only your server knows, such as payment failures,
cancellations and plan changes, and to confirm contact consent with the
server key.
Install
Section titled “Install”pip install whisperrThe package is whisperr on PyPI. The
current version is 0.2.1. It needs Python 3.8 or later and has no runtime
dependencies. For Django, pip install "whisperr[django]" also installs
Django 3.2 or later.
Quickstart
Section titled “Quickstart”import osfrom whisperr import Whisperr
# WHISPERR_API_KEY is the server key (wrk_…).whisperr = Whisperr(api_key=os.environ["WHISPERR_API_KEY"])
# A server-side event:whisperr.track("user_8842", "payment_failed", {"amount_cents": 4900, "reason": "card_declined"})
# Traits and a contact the user agreed to:whisperr.identify( "user_8842", traits={"first_name": "Ada", "plan": "pro"}, channels=[{ "type": "email", "address": "ada@example.com", "opted_in": True, "verified": True, }],)
# Before the process exits (also registered with atexit):whisperr.shutdown()Create the server key as a Server key in Settings → API keys. Hold one client for the life of the process.
track(external_user_id, event_type, properties=None, context=None) always
takes the user id first. Pass the same id your app passes to identify(), and
backend and frontend events land on
one timeline. Event names must be
lowercase snake_case; the SDK drops any other name and reports it to
on_error as dropped.
How do I confirm contact consent with the Python SDK?
Section titled “How do I confirm contact consent with the Python SDK?”The Python SDK runs with the server key, so its identify() is how you
confirm consent. Send "opted_in": True only when the user agreed to
messages on that channel, and "verified": True only for an email address
your app confirmed. Send "opted_in": False to record an opt-out.
See Contact consent.
Django
Section titled “Django”WHISPERR_API_KEY = os.environ["WHISPERR_API_KEY"]
MIDDLEWARE = [ # ... after django.contrib.auth.middleware.AuthenticationMiddleware ... "whisperr.django.WhisperrMiddleware",]def upgrade(request): request.whisperr.track("plan_upgraded", {"plan": "pro"}) ...request.whisperr.track() and request.whisperr.identify() use the
authenticated user, str(request.user.pk) by default. When the user is not
signed in, the calls do nothing. To read the id from somewhere else, set
WHISPERR_RESOLVE_USER to the dotted path of a function
resolve(request) -> str | None.
For work outside a request, such as Celery tasks, webhooks and management commands, use the shared client with the user id from your own data:
from whisperr.django import get_client
get_client().track(str(subscription.user_id), "subscription_cancelled", {"reason": reason})Django settings the integration reads:
| Setting | Default | Notes |
|---|---|---|
WHISPERR_API_KEY | "" | Server key (wrk_…). |
WHISPERR_BASE_URL | https://api.whisperr.net | Ingestion base URL. |
WHISPERR_APP_VERSION | — | Sent as context.app_version. |
WHISPERR_DISABLED | False | Captures and sends nothing. |
WHISPERR_DEBUG | False | Logs warnings to stderr. |
WHISPERR_RESOLVE_USER | — | Dotted path to a user-id resolver. |
How do I set the message language from Python?
Section titled “How do I set the message language from Python?”Pass the language the user picked in your product as the app_language
trait. Use a BCP 47 tag such as de or pt-BR. The Python SDK has no
separate method for it and never guesses a language.
whisperr.identify("user_8842", traits={"app_language": "pt-BR"})What the Python SDK adds to every event
Section titled “What the Python SDK adds to every event”context key | Value |
|---|---|
sdk_name | whisperr-python |
sdk_version | The SDK version, for example 0.2.1 |
app_version | The app_version option. Not set: no key. |
A key you pass in the context argument of track() wins over these.
How do I send billing events from Python?
Section titled “How do I send billing events from Python?”When Whisperr has no billing source connected for your app (such as
RevenueCat), send membership changes from your
backend with the reserved names in BillingEvent. Never send them from the
browser or the app.
from whisperr import BillingEvent
whisperr.track( "user_8842", BillingEvent.MEMBERSHIP_STARTED, { "plan_id": "pro_monthly", "price": 9.99, # major units, not cents "currency": "USD", # ISO 4217, upper case "plan_period": "P1M", # ISO 8601 duration "transaction_id": "txn_123", "store": "stripe", "is_first_ever": True, "access_source": "self_subscription", "current_period_ends_at": "2026-11-12T10:00:00Z", },)BillingEvent has TRIAL_STARTED, TRIAL_CONVERTED,
TRIAL_AUTO_RENEW_DISABLED, MEMBERSHIP_STARTED, RENEWAL_SUCCEEDED,
AUTO_RENEW_DISABLED, AUTO_RENEW_ENABLED, MEMBERSHIP_ENDED,
BILLING_ISSUE_STARTED, BILLING_RECOVERED and REFUND_GRANTED. The
Node SDK page lists the
properties of each event and the closed value lists.
Register these event names for your app; setup with your AI agent registers the events your app sends. Whisperr stores an event with an unregistered name but does not map it to your app’s events.
Short-lived processes
Section titled “Short-lived processes”In scripts, serverless handlers and management commands, call flush() or
shutdown() before the process ends:
whisperr.track(user_id, "report_generated")whisperr.flush()flush() blocks until the queue is sent or sending cannot continue.
How does the Python SDK deliver events?
Section titled “How does the Python SDK deliver events?”- Events go to
POST /v1/events/batchand identify calls toPOST /v1/identify, in order, with theX-API-Keyheader. - Every event has a
$message_idthat stays the same across retries, so Whisperr removes duplicates. 429,5xxand network errors are retried with exponential backoff, up to 30 seconds between tries. Aftermax_retries, the batch stays queued for the next flush. The Python SDK does not readRetry-After.- On
401or403the SDK stops sending and keeps the batch. Other4xxresponses drop the batch and reportdropped. - The queue is not written to disk. A crash loses unsent events.
See Delivery, retries, idempotency.
Options
Section titled “Options”| Option | Default | Notes |
|---|---|---|
api_key | — | Server key (wrk_…). Required unless disabled. |
base_url | https://api.whisperr.net | Ingestion base URL. |
app_version | — | Your release version. Sent as context.app_version. |
flush_at | 20 | Send when this many events are queued. |
flush_interval | 10.0 | Background send interval (seconds). |
max_queue_size | 10000 | Oldest events drop when the queue is full. |
max_batch_size | 500 | Events per batch request. 500 is the maximum. |
max_retries | 6 | Retries before the batch waits for the next flush. |
request_timeout | 10.0 | Per-request timeout (seconds). |
disabled | False | Captures and sends nothing. Useful in tests. |
debug | False | Logs warnings to stderr. |
on_error | — | Callable[[WhisperrError], None]. error.type is auth, dropped or retry_exhausted. |
Common questions
Which API key does the Python SDK use?
The server key (wrk_…), shown as a Server key in Settings → API keys. Keep it in your backend secrets. It can always opt a contact in.
Does the Python SDK block my request while it sends?
No. track() and identify() add to an in-memory queue and return at once. A background daemon thread sends the queue.
Which Python versions does the whisperr package support?
Python 3.8 or later. The package uses only the standard library.
How do I send events from a Celery task with the Django integration?
Call whisperr.django.get_client() to get the process-wide client, then call track() with the user id from your own data.