Skip to content

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.

pip install whisperr

The 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.

whisperr_client.py
import os
from 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.

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.

settings.py
WHISPERR_API_KEY = os.environ["WHISPERR_API_KEY"]
MIDDLEWARE = [
# ... after django.contrib.auth.middleware.AuthenticationMiddleware ...
"whisperr.django.WhisperrMiddleware",
]
views.py
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:

tasks.py
from whisperr.django import get_client
get_client().track(str(subscription.user_id), "subscription_cancelled", {"reason": reason})

Django settings the integration reads:

SettingDefaultNotes
WHISPERR_API_KEY""Server key (wrk_…).
WHISPERR_BASE_URLhttps://api.whisperr.netIngestion base URL.
WHISPERR_APP_VERSION—Sent as context.app_version.
WHISPERR_DISABLEDFalseCaptures and sends nothing.
WHISPERR_DEBUGFalseLogs 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"})
context keyValue
sdk_namewhisperr-python
sdk_versionThe SDK version, for example 0.2.1
app_versionThe app_version option. Not set: no key.

A key you pass in the context argument of track() wins over these.

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.

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.

  • Events go to POST /v1/events/batch and identify calls to POST /v1/identify, in order, with the X-API-Key header.
  • Every event has a $message_id that stays the same across retries, so Whisperr removes duplicates.
  • 429, 5xx and network errors are retried with exponential backoff, up to 30 seconds between tries. After max_retries, the batch stays queued for the next flush. The Python SDK does not read Retry-After.
  • On 401 or 403 the SDK stops sending and keeps the batch. Other 4xx responses drop the batch and report dropped.
  • The queue is not written to disk. A crash loses unsent events.

See Delivery, retries, idempotency.

OptionDefaultNotes
api_key—Server key (wrk_…). Required unless disabled.
base_urlhttps://api.whisperr.netIngestion base URL.
app_version—Your release version. Sent as context.app_version.
flush_at20Send when this many events are queued.
flush_interval10.0Background send interval (seconds).
max_queue_size10000Oldest events drop when the queue is full.
max_batch_size500Events per batch request. 500 is the maximum.
max_retries6Retries before the batch waits for the next flush.
request_timeout10.0Per-request timeout (seconds).
disabledFalseCaptures and sends nothing. Useful in tests.
debugFalseLogs 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.