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.
pip install mbuzz
Requires Python 3.9+.
import mbuzz mbuzz.init(api_key="sk_live_...")
Call init() once on app boot. For Flask apps, see Flask Integration below.
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")
Record revenue-generating outcomes:
mbuzz.conversion( "purchase", revenue=99.99, funnel="purchase", # optional: group into funnel order_id=order.id, )
Link visitors to known users (enables cross-device attribution):
# On signup or login mbuzz.identify( user.id, traits={ "email": user.email, "name": user.name, }, )
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
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)
# 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].
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].
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.
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.
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, )
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 )
| 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 |
mbuzz never raises exceptions. All methods fail silently and log errors in debug mode — your application flow is never interrupted by tracking failures.
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
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.
- Python 3.9+
- No required dependencies for the core SDK
flask,djangoorstarletteonly required for that framework's middleware
PRs welcome — especially framework adapters (Django, FastAPI, Starlette) and async support.
MIT License