Swetrix
Integrations

FastAPI

Integrate Swetrix with your FastAPI application to track page views, monitor errors, and capture custom events — all while staying privacy-friendly and GDPR-compliant.

Unlike client-side integrations that inject a tracking script into the browser, this approach sends analytics data directly from your server using the Swetrix Events API. This means tracking works even when visitors have JavaScript disabled or use ad blockers, and no third-party scripts are loaded in the browser.

Installation

Install httpx for making async HTTP requests to the Swetrix API:

pip install httpx

Setup

Create a reusable Swetrix client that wraps the Events API:

import httpx
from typing import Optional


class SwetrixClient:
    def __init__(
        self,
        project_id: str,
        api_url: str = "https://api.swetrix.com/log",
        disabled: bool = False,
    ):
        self.project_id = project_id
        self.api_url = api_url
        self.disabled = disabled
        self._client = httpx.AsyncClient(timeout=10.0)

    async def track_pageview(
        self,
        ip: str,
        user_agent: str,
        *,
        pg: Optional[str] = None,
        lc: Optional[str] = None,
        ref: Optional[str] = None,
        so: Optional[str] = None,
        me: Optional[str] = None,
        ca: Optional[str] = None,
        unique: bool = False,
    ):
        if self.disabled:
            return

        payload = {"pid": self.project_id}
        if pg:
            payload["pg"] = pg
        if lc:
            payload["lc"] = lc
        if ref:
            payload["ref"] = ref
        if so:
            payload["so"] = so
        if me:
            payload["me"] = me
        if ca:
            payload["ca"] = ca
        if unique:
            payload["unique"] = True

        await self._client.post(
            self.api_url,
            json=payload,
            headers={
                "User-Agent": user_agent or "",
                "X-Client-IP-Address": ip,
                "Content-Type": "application/json",
            },
        )

    async def track_event(
        self,
        ip: str,
        user_agent: str,
        *,
        ev: str,
        pg: Optional[str] = None,
        unique: bool = False,
        meta: Optional[dict] = None,
    ):
        if self.disabled:
            return

        payload = {"pid": self.project_id, "ev": ev}
        if pg:
            payload["pg"] = pg
        if unique:
            payload["unique"] = True
        if meta:
            payload["meta"] = meta

        await self._client.post(
            f"{self.api_url}/custom",
            json=payload,
            headers={
                "User-Agent": user_agent or "",
                "X-Client-IP-Address": ip,
                "Content-Type": "application/json",
            },
        )

    async def track_error(
        self,
        ip: str,
        user_agent: str,
        *,
        name: str,
        message: Optional[str] = None,
        stack_trace: Optional[str] = None,
        pg: Optional[str] = None,
        lc: Optional[str] = None,
        meta: Optional[dict] = None,
    ):
        if self.disabled:
            return

        payload = {"pid": self.project_id, "name": name}
        if message:
            payload["message"] = message
        if stack_trace:
            payload["stackTrace"] = stack_trace
        if pg:
            payload["pg"] = pg
        if lc:
            payload["lc"] = lc
        if meta:
            payload["meta"] = meta

        await self._client.post(
            f"{self.api_url}/error",
            json=payload,
            headers={
                "User-Agent": user_agent or "",
                "X-Client-IP-Address": ip,
                "Content-Type": "application/json",
            },
        )

    async def heartbeat(self, ip: str, user_agent: str):
        if self.disabled:
            return

        await self._client.post(
            f"{self.api_url}/hb",
            json={"pid": self.project_id},
            headers={
                "User-Agent": user_agent or "",
                "X-Client-IP-Address": ip,
                "Content-Type": "application/json",
            },
        )

    async def close(self):
        await self._client.aclose()

Replace YOUR_PROJECT_ID with your actual Project ID from the Swetrix dashboard, otherwise tracking won't work.

Application setup

Initialise the client and wire it into your FastAPI app using lifespan events:

import os
from contextlib import asynccontextmanager
from fastapi import FastAPI


@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.swetrix = SwetrixClient(
        project_id=os.environ["SWETRIX_PROJECT_ID"],
        disabled=os.environ.get("ENVIRONMENT") != "production",
    )
    yield
    await app.state.swetrix.close()


app = FastAPI(lifespan=lifespan)

Tracking pageviews

To track pageviews you need to pass the visitor's IP address and User-Agent header. Without these, unique visitor counting and live visitor tracking won't work. See the Events API docs for details.

Basic example

from fastapi import Request


@app.get("/")
async def homepage(request: Request):
    swetrix = request.app.state.swetrix
    ip = request.client.host
    user_agent = request.headers.get("user-agent", "")

    await swetrix.track_pageview(
        ip,
        user_agent,
        pg="/",
        lc=request.headers.get("accept-language", "").split(",")[0] or None,
        ref=request.headers.get("referer"),
    )

    return {"message": "Hello World"}

FastAPI middleware lets you track pageviews automatically on every request without repeating code in each route:

from fastapi import Request


@app.middleware("http")
async def track_pageviews(request: Request, call_next):
    swetrix = request.app.state.swetrix
    ip = request.client.host
    user_agent = request.headers.get("user-agent", "")

    # Fire-and-forget — don't block the response
    import asyncio
    asyncio.create_task(
        swetrix.track_pageview(
            ip,
            user_agent,
            pg=request.url.path,
            lc=request.headers.get("accept-language", "").split(",")[0] or None,
            ref=request.headers.get("referer"),
        )
    )

    response = await call_next(request)
    return response

By using asyncio.create_task(), the analytics call runs in the background without adding latency to your responses. This is the recommended pattern for production.

If your FastAPI app runs behind a reverse proxy (e.g. Nginx, Cloudflare, or a load balancer), configure trusted hosts and use the X-Forwarded-For header to get the real client IP:

@app.middleware("http")
async def track_pageviews(request: Request, call_next):
    ip = request.headers.get("x-forwarded-for", "").split(",")[0].strip()
    if not ip:
        ip = request.client.host

    # ... rest of tracking logic

Dependency injection approach

For more control, you can use FastAPI's dependency injection to make the client IP and user agent available in your routes:

from fastapi import Depends, Request
from dataclasses import dataclass


@dataclass
class VisitorInfo:
    ip: str
    user_agent: str


def get_visitor_info(request: Request) -> VisitorInfo:
    ip = request.headers.get("x-forwarded-for", "").split(",")[0].strip()
    if not ip:
        ip = request.client.host
    return VisitorInfo(
        ip=ip,
        user_agent=request.headers.get("user-agent", ""),
    )


@app.get("/dashboard")
async def dashboard(request: Request, visitor: VisitorInfo = Depends(get_visitor_info)):
    await request.app.state.swetrix.track_pageview(
        visitor.ip,
        visitor.user_agent,
        pg="/dashboard",
    )
    return {"page": "dashboard"}

Pageview options

The track_pageview method accepts the following keyword arguments:

OptionTypeDescription
pgstrPage path (e.g. /home)
lcstrVisitor locale (e.g. en-US)
refstrReferrer URL
sostrTraffic source (e.g. utm_source)
mestrTraffic medium (e.g. utm_medium)
castrCampaign (e.g. utm_campaign)
uniqueboolOnly save unique visits

Tracking custom events

Track specific actions — API calls, form submissions, purchases, etc.:

from pydantic import BaseModel


class SubscribeRequest(BaseModel):
    email: str
    plan: str


@app.post("/api/subscribe")
async def subscribe(body: SubscribeRequest, request: Request):
    swetrix = request.app.state.swetrix
    ip = request.client.host
    user_agent = request.headers.get("user-agent", "")

    import asyncio
    asyncio.create_task(
        swetrix.track_event(
            ip,
            user_agent,
            ev="NEWSLETTER_SUBSCRIBE",
            pg="/subscribe",
            meta={"plan": body.plan},
        )
    )

    return {"success": True}

Event naming rules

Event names must:

  • Contain any characters (including spaces, unicode, etc.)
  • Be no longer than 256 characters

We recommend UPPER_SNAKE_CASE for consistency (e.g. NEWSLETTER_SUBSCRIBE, CHECKOUT_COMPLETED).

Error tracking

Use a custom exception handler to report errors to Swetrix automatically:

from fastapi import Request
from fastapi.responses import JSONResponse


@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    import traceback

    swetrix = request.app.state.swetrix

    import asyncio
    asyncio.create_task(
        swetrix.track_error(
            request.client.host,
            request.headers.get("user-agent", ""),
            name=type(exc).__name__,
            message=str(exc),
            stack_trace=traceback.format_exc(),
            pg=request.url.path,
        )
    )

    return JSONResponse(
        status_code=500,
        content={"error": "Internal Server Error"},
    )

You can also track errors from specific routes:

@app.get("/api/data")
async def get_data(request: Request):
    try:
        data = await fetch_external_data()
        return data
    except Exception as exc:
        import traceback
        import asyncio

        asyncio.create_task(
            request.app.state.swetrix.track_error(
                request.client.host,
                request.headers.get("user-agent", ""),
                name=type(exc).__name__,
                message=str(exc),
                stack_trace=traceback.format_exc(),
                pg="/api/data",
            )
        )
        return JSONResponse(
            status_code=500,
            content={"error": "Failed to fetch data"},
        )

Heartbeat events

Heartbeat events let Swetrix know a visitor's session is still active, powering the Live Visitors counter in your dashboard. They're most useful in long-lived connections like WebSocket sessions:

import asyncio
from fastapi import WebSocket


HEARTBEAT_INTERVAL = 30


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    ip = websocket.client.host
    user_agent = websocket.headers.get("user-agent", "")
    swetrix = websocket.app.state.swetrix

    async def send_heartbeats():
        while True:
            await swetrix.heartbeat(ip, user_agent)
            await asyncio.sleep(HEARTBEAT_INTERVAL)

    heartbeat_task = asyncio.create_task(send_heartbeats())

    try:
        while True:
            await websocket.receive_text()
    except Exception:
        heartbeat_task.cancel()

Disable tracking in development

Use the disabled option to prevent tracking during development:

swetrix = SwetrixClient(
    project_id=os.environ["SWETRIX_PROJECT_ID"],
    disabled=os.environ.get("ENVIRONMENT") != "production",
)

Using environment variables for your Project ID

Rather than hardcoding the Project ID, store it in an environment variable:

export SWETRIX_PROJECT_ID=YOUR_PROJECT_ID

If you use a .env file with python-dotenv:

SWETRIX_PROJECT_ID=YOUR_PROJECT_ID
ENVIRONMENT=production
from dotenv import load_dotenv

load_dotenv()

Or with Pydantic Settings, which is common in FastAPI projects:

from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    swetrix_project_id: str
    environment: str = "development"

    class Config:
        env_file = ".env"


settings = Settings()

swetrix = SwetrixClient(
    project_id=settings.swetrix_project_id,
    disabled=settings.environment != "production",
)

Complete example

Here's a full FastAPI application with Swetrix analytics:

import os
import asyncio
import traceback
from contextlib import asynccontextmanager

import httpx
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel


class SwetrixClient:
    def __init__(self, project_id: str, api_url: str = "https://api.swetrix.com/log", disabled: bool = False):
        self.project_id = project_id
        self.api_url = api_url
        self.disabled = disabled
        self._client = httpx.AsyncClient(timeout=10.0)

    async def track_pageview(self, ip: str, user_agent: str, **kwargs):
        if self.disabled:
            return
        payload = {"pid": self.project_id, **{k: v for k, v in kwargs.items() if v is not None}}
        await self._client.post(
            self.api_url,
            json=payload,
            headers={"User-Agent": user_agent or "", "X-Client-IP-Address": ip, "Content-Type": "application/json"},
        )

    async def track_event(self, ip: str, user_agent: str, *, ev: str, **kwargs):
        if self.disabled:
            return
        payload = {"pid": self.project_id, "ev": ev, **{k: v for k, v in kwargs.items() if v is not None}}
        await self._client.post(
            f"{self.api_url}/custom",
            json=payload,
            headers={"User-Agent": user_agent or "", "X-Client-IP-Address": ip, "Content-Type": "application/json"},
        )

    async def track_error(self, ip: str, user_agent: str, *, name: str, **kwargs):
        if self.disabled:
            return
        payload = {"pid": self.project_id, "name": name}
        if "stack_trace" in kwargs:
            kwargs["stackTrace"] = kwargs.pop("stack_trace")
        payload.update({k: v for k, v in kwargs.items() if v is not None})
        await self._client.post(
            f"{self.api_url}/error",
            json=payload,
            headers={"User-Agent": user_agent or "", "X-Client-IP-Address": ip, "Content-Type": "application/json"},
        )

    async def close(self):
        await self._client.aclose()


@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.swetrix = SwetrixClient(
        project_id=os.environ.get("SWETRIX_PROJECT_ID", "YOUR_PROJECT_ID"),
        disabled=os.environ.get("ENVIRONMENT") != "production",
    )
    yield
    await app.state.swetrix.close()


app = FastAPI(lifespan=lifespan)


@app.middleware("http")
async def track_pageviews(request: Request, call_next):
    ip = request.headers.get("x-forwarded-for", "").split(",")[0].strip()
    if not ip:
        ip = request.client.host
    user_agent = request.headers.get("user-agent", "")

    asyncio.create_task(
        request.app.state.swetrix.track_pageview(
            ip,
            user_agent,
            pg=request.url.path,
            lc=request.headers.get("accept-language", "").split(",")[0] or None,
            ref=request.headers.get("referer"),
        )
    )

    response = await call_next(request)
    return response


@app.get("/")
async def homepage():
    return {"message": "Hello World"}


class ContactRequest(BaseModel):
    name: str
    email: str


@app.post("/api/contact")
async def contact(body: ContactRequest, request: Request):
    ip = request.client.host
    user_agent = request.headers.get("user-agent", "")

    asyncio.create_task(
        request.app.state.swetrix.track_event(
            ip, user_agent, ev="CONTACT_FORM_SUBMITTED", pg="/contact"
        )
    )

    return {"success": True}


@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    asyncio.create_task(
        request.app.state.swetrix.track_error(
            request.client.host,
            request.headers.get("user-agent", ""),
            name=type(exc).__name__,
            message=str(exc),
            stack_trace=traceback.format_exc(),
            pg=request.url.path,
        )
    )

    return JSONResponse(status_code=500, content={"error": "Internal Server Error"})

Run it with:

uvicorn main:app --reload

Check your installation

Deploy your application and make a few requests. Within a minute you should see new pageviews appearing in your Swetrix dashboard.

Self-hosted Swetrix

If you're self-hosting the Swetrix API, point the api_url option to your instance:

swetrix = SwetrixClient(
    project_id="YOUR_PROJECT_ID",
    api_url="https://your-swetrix-instance.com/log",
)

Further reading

On this page