Skip to main content

Sending events

Three messengers carry data to Umami. new_event() reports that something happened. new_page_view() reports that someone looked at a page. new_revenue_event() reports that something happened and money changed hands. Each has an _async twin with the same signature and the same return value, and none of them need a login. They need set_url_base() (or set_cloud_api_key()), a website id, and a hostname, and that is all.

All three share the same arguments, because they fill in the same session fields a browser would:

Argument Default Notes
hostname value from set_hostname() The domain visitors see. Required one way or the other.
website_id value from set_website_id() Required one way or the other.
url '/' for events, required for page views The page path the event is attributed to.
referrer '' Shown in the Referrers section when set.
language 'en-US' Recorded on the session.
screen '1920x1080' Recorded on the session.
ip_address not sent Sent as ip when non-blank, so Umami geolocates the real visitor instead of your server.
distinct_id not sent A stable per-user id. See below.

Missing hostname or website_id raises umami.errors.ValidationError before any request is made. The package would rather fail in your process than hand Umami an event it cannot file.

Custom events

A custom event is the headline feature. The browser tracker records clicks; this records the things your code knows that a click never reveals: a webhook confirmed a payment, a background job finished, a trial converted.

umami.new_event(
    event_name='checkout-completed',
    url='/checkout',
    title='Checkout',            # defaults to event_name
    custom_data={'plan': 'pro', 'seats': 3},
    referrer='https://news.ycombinator.com',
)

event_name is required and must not be blank. title is the page title Umami records alongside the event and defaults to the event name. custom_data becomes the event’s properties. They are not shown in the main dashboard view, but they are there: click into the event, or read them through the Umami API.

Page views

A page view is what the browser tracker records when a page loads. Send one from the server when you render for a client that cannot run JavaScript, such as a feed reader or a developer with curl, or when you serve content outside a browser altogether.

umami.new_page_view(page_title='Account details', url='/account/details', ip_address=request.client.host)

page_title and url are both required. Page views have no event name and no custom data.

new_page_view() accepts one argument the others do not: ua, the user agent recorded for the visitor. It defaults to a desktop Chrome user agent, because Umami drops requests whose user agent looks like a bot, and a library’s default user agent is exactly what a bot looks like. Pass the real browser’s user agent when you have it so the Browsers and Devices sections tell the truth. new_event() has no ua argument.

Revenue

new_revenue_event() is new_event() with two keys added to custom_data: revenue and currency. Umami’s revenue report reads those two keys, which is why they get their own function and their own validation.

umami.new_revenue_event(
    revenue=19.99,
    currency='USD',              # ISO 4217, defaults to 'USD'
    event_name='checkout-cart',  # defaults to 'revenue'
    url='/checkout',
    custom_data={'product': 'widget', 'quantity': 2},
)

Validation before the request: revenue must be an int or float greater than or equal to zero, and currency must be non-blank. If your custom_data already has revenue or currency keys, the arguments win and overwrite them. A negative amount is rejected, so a refund needs its own event name rather than a minus sign.

Per-user attribution with distinct_id

Without an id, Umami identifies visitors on its own from the session fields. Pass distinct_id on any of the three functions and Umami can tie sessions and events to one known person instead, across devices. It is sent as the payload field id.

umami.new_event(event_name='login', distinct_id=user.id)

Rules the package enforces:

  • str and int are accepted. Integers are sent as strings.
  • Blank or whitespace-only strings are ignored and no id is sent.
  • bool and any other type raise ValidationError. The bool check is explicit because in Python True is an int, and distinct_id=True is never what anyone meant.

Use an internal user id or a hash, not an email address or other raw personal data. Umami is privacy-preserving analytics; keep it that way.

What gets sent

Every call makes exactly one POST to /api/send. No batching, no retry, no queue. Here is the envelope for the revenue example above:

{
  "type": "event",
  "payload": {
    "website": "978435e2-7ba1-4337-9860-ec31ece2db60",
    "hostname": "example.com",
    "url": "/checkout",
    "title": "checkout-cart",
    "name": "checkout-cart",
    "data": {"product": "widget", "quantity": 2, "revenue": 19.99, "currency": "USD"},
    "referrer": "",
    "language": "en-US",
    "screen": "1920x1080",
    "id": "user-42"
  }
}

A page view is the same shape without name and data. The ip field is present only when you pass ip_address, and id only when you pass distinct_id. If you ever need to argue with Umami about what it received, this is what you sent.

Return values

The functions return Umami’s JSON response as a dict. When tracking has been turned off with disable() they validate their arguments and return {} without making a request, which is how you tell “sent” from “skipped”. A non-2xx response raises httpx2.HTTPStatusError.

Async

Each function has an _async twin that sends the same payload and returns the same value. The package’s own tests build both and compare the request bodies, so the twins cannot drift apart quietly.

await umami.new_event_async(event_name='checkout-completed', url='/checkout')
await umami.new_page_view_async(page_title='Home', url='/')
await umami.new_revenue_event_async(revenue=19.99, url='/checkout')

The configuration functions are synchronous only. Call set_url_base() and friends once at startup, before your event loop starts handling requests.

If an event does not appear in the dashboard, see Troubleshooting. You are not the first.