mbuzz
Server-side multi-touch attribution for Python. Track customer journeys, attribute conversions, know which channels drive revenue.
Unlike client-side analytics, mbuzz runs server-side — no ad-blocker blind spots, no iOS tracking gaps, no browser privacy loss. Captures 30–40% more touchpoints than client-side tools.
Installation
pip install mbuzz
Requires Python 3.9+.
Quick Start
1. Initialize
import mbuzz
mbuzz.init(api_key="sk_live_...")
Call init() once on app boot. For Flask apps, see Flask Integration below.
2. Track Events
Track steps in the customer journey:
mbuzz.event("page_view", url="/pricing")
mbuzz.event("add_to_cart", product_id="SKU-123", price=49.99)
mbuzz.event("checkout_started", cart_total=99.99)
# Group events into funnels for focused analysis
mbuzz.event("signup_start", funnel="signup", source="homepage")
mbuzz.event("signup_complete", funnel="signup")
3. Track Conversions
Record revenue-generating outcomes:
mbuzz.conversion(
"purchase",
revenue=99.99,
funnel="purchase", # optional: group into funnel
order_id=order.id,
)
4. Identify Users
Link visitors to known users (enables cross-device attribution):
# On signup or login
mbuzz.identify(
user.id,
traits={
"email": user.email,
"name": user.name,
},
)
Funnels
Group related events into funnels for focused conversion analysis in your dashboard.
# Signup funnel
mbuzz.event("pricing_view", funnel="signup")
mbuzz.event("signup_start", funnel="signup")
mbuzz.event("signup_complete", funnel="signup")
# Purchase funnel
mbuzz.event("add_to_cart", funnel="purchase")
mbuzz.event("checkout_started", funnel="purchase")
mbuzz.conversion("purchase", funnel="purchase", revenue=99.99)
Why funnels?
- Separate signup flow from purchase flow
- Analyze each conversion path independently
- Filter dashboard to specific customer journeys
Flask Integration
mbuzz ships with a Flask middleware that handles visitor cookies, session resolution, and request context automatically.
from flask import Flask
import mbuzz
from mbuzz.middleware.flask import init_app
app = Flask(__name__)
mbuzz.init(api_key="sk_live_...")
init_app(app)
After init_app, every request carries a visitor_id in context. Track events from anywhere in your request lifecycle:
@app.route("/pricing")
def pricing():
mbuzz.event("page_view", page="/pricing")
return render_template("pricing.html")
Access the current visitor or user from context:
mbuzz.visitor_id() # Current visitor ID (from cookie)
mbuzz.user_id() # Current user ID (if identify() was called)
Django Integration
# settings.py
MIDDLEWARE = [
# ...
"mbuzz.middleware.django.MbuzzMiddleware",
]
Initialise the SDK once at startup — in your AppConfig.ready() or settings.py:
import mbuzz
mbuzz.init(api_key="sk_live_...")
Install with pip install mbuzz[django].
FastAPI Integration
Works with any Starlette app; FastAPI is Starlette underneath.
from fastapi import FastAPI
import mbuzz
from mbuzz.middleware.fastapi import MbuzzMiddleware
app = FastAPI()
mbuzz.init(api_key="sk_live_...")
app.add_middleware(MbuzzMiddleware)
Install with pip install mbuzz[fastapi].
Other Frameworks
The core mbuzz.event, mbuzz.conversion, and mbuzz.identify functions are framework-agnostic. You can call them from any Python web app or background job.
Full-page caching — the snippet below is REQUIRED
Add this to your base template or nothing is tracked. Since 0.10.0 a page response never
sets the visitor cookie: only POST /_mbuzz/session mints, and the snippet is what calls it.
A page response can be stored by a full-page cache (Cloudflare, Varnish, nginx, a CDN) and
replayed to every visitor. A Set-Cookie sitting in that cache hands everyone the first
visitor's id, so unrelated people merge into one journey — corruption rather than loss, since
every row exists and is simply attributed to the wrong person. Minting only on a response no
cache stores is the only way to prevent it.
The same endpoint solves the original problem too: a cached page never enters your application, so the middleware cannot run — but this one request always reaches it.
The middleware answers POST /_mbuzz/session on all three frameworks. There is nothing extra
to mount. Call it once per page, from your base template:
<script>
fetch('/_mbuzz/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: location.href, referrer: document.referrer || '' }),
credentials: 'same-origin',
keepalive: true
}).catch(function () {});
</script>
Two things to keep as they are:
- Inline the script, don't load it as a file. Asset optimisers delay external scripts until the visitor first interacts. A visitor who lands and converts without clicking anything first would never be established.
credentials: 'same-origin'is required, or the cookie never comes back.
The visitor id is never created or read in JavaScript. It stays HttpOnly and server-set, which
is what preserves its full two-year life — a cookie written by document.cookie is capped at
7 days under Safari's ITP, and 24 hours after an ad click.
Background Jobs
For tracking from Celery, RQ, or other background workers, pass visitor_id explicitly:
# Capture at request time
@app.route("/checkout")
def checkout():
visitor_id = mbuzz.visitor_id()
process_order.delay(order.id, visitor_id)
return redirect("/thank-you")
# Use in the job
@celery.task
def process_order(order_id, visitor_id):
order = Order.get(order_id)
mbuzz.conversion(
"purchase",
visitor_id=visitor_id,
revenue=order.total,
)
Configuration Options
mbuzz.init(
api_key="sk_live_...", # Required
enabled=True, # Default: True — toggle without code changes
debug=False, # Default: False — enables verbose logging
timeout=5.0, # Default: 5.0s — API request timeout
skip_paths=["/health"], # Optional — paths to skip tracking
skip_extensions=[".css"], # Optional — file extensions to skip
)
The 4-Call Model
| Method | When to Use |
|---|---|
init |
Once on app boot |
event |
User interactions, funnel steps |
conversion |
Purchases, signups, any revenue event |
identify |
Login, signup, when you know the user |
Error Handling
mbuzz never raises exceptions. All methods fail silently and log errors in debug mode — your application flow is never interrupted by tracking failures.
Why Server-Side?
Client-side tracking loses 30–40% of marketing data to ad blockers, iOS tracking protections, and 7-day cookie caps. Server-side attribution runs inside your application, where requests flow through your own domain as first-party data — so you see the full customer journey.
Full explainer: mbuzz.co/articles/server-side-vs-client-side-tracking
Attribution Models
mbuzz runs 8 attribution models side-by-side out of the box — first-touch, last-touch, linear, time-decay, position-based, Markov, Shapley, and data-driven — so you can see how much each model disagrees about which channel deserves credit.
Requirements
- Python 3.9+
- No required dependencies for the core SDK
flask,djangoorstarletteonly required for that framework's middleware
Links
Contributing
PRs welcome — especially framework adapters (Django, FastAPI, Starlette) and async support.
License
MIT License
Metadata
Release files for mbuzz 0.10.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mbuzz-0.10.0.tar.gz | 98.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mbuzz-0.10.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 120.5 kB
Release files / mbuzz-0.10.0.tar.gz
| Download URL | mbuzz-0.10.0.tar.gz |
|---|---|
| Size | 98.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d920204bddf4ea1ebfc29f943a4346ecf01ffba24dabc3da4208875ed58591cc
|
|
BLAKE2b-256 checksum How to use checksums |
fd58a623b72a7ee02c0f08975e85d307d87adc265cede6d8894ac7c979278635
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / mbuzz-0.10.0-py3-none-any.whl
| Download URL | mbuzz-0.10.0-py3-none-any.whl |
|---|---|
| Size | 22.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1486755211a24fe4b95ee6163a0fdc18d0d5d28fd528e3979e98cffd20ff5f4b
|
|
BLAKE2b-256 checksum How to use checksums |
c30ff20d163a98e9b197f2d14d92c411e17c8415438e0124cd417a158b39652f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|