Tracking Matomo from your server: the HTTP Tracking API and token_auth

The event happened on your server, not in a browser, so the JavaScript snippet never saw it. Here's how to record it with Matomo's HTTP Tracking API: the parameters that actually matter, when you need token_auth, and whether you can stop strangers spamming the public matomo.php endpoint.

The order was confirmed by a webhook. The signup came in through a mobile app calling your REST API. The refund got processed in a back-office tool nobody opens in a browser. None of it shows up in Matomo, because the JavaScript snippet that does your tracking never ran. There was no page, no browser, no matomo.js to hook into.

You don't fix this by faking browser requests from the server. Matomo has a first-class way to record events from anywhere your code runs: the HTTP Tracking API. It's a plain HTTP request to matomo.php that you can send from your own code in any language, on any platform. The parts that trip people up are always the same: which parameters you actually need, when you need token_auth, and whether you can stop strangers spamming a public endpoint.

TL;DR

Send a plain HTTP request to matomo.php with idsite and rec=1, and you can record events from any backend or app where the JavaScript snippet never runs. You only need token_auth for trusted overrides like cip (the real client IP); ordinary hits carry no token. And no token can lock the endpoint down: as long as a browser snippet posts to matomo.php, it has to accept anonymous hits.

Three things people call "the tracking code"

Before the API itself, it's worth separating the things that all get called "tracking" in Matomo, because mixing them up is its own source of bugs:

  • _paq: the JavaScript Tracking Client in matomo.js. A low-level API you call in the browser.
  • _mtm: the Matomo Tag Manager data layer. You push events onto it; a Trigger then fires a tag that does the actual tracking. A bare _mtm.push does nothing on its own until that tag is wired up.
  • The HTTP Tracking API: an HTTP request to matomo.php from your own code, no browser involved.

The first two only run in the visitor's browser. The third runs wherever you run it, which is why it's the one you want for backend and app tracking. (And don't run both a standalone matomo.js snippet and a Tag Manager container that also tracks page views, or every pageview counts twice.)

The HTTP Tracking API, minimally

Every hit is a GET or POST to one endpoint:

text
https://your-matomo.example/matomo.php

Exactly two parameters are mandatory:

  • idsite: the site ID you're recording against
  • rec=1: "record this hit"

Everything else is optional, but you'll want a few. Here's a realistic server-side hit you can paste into a terminal. curl -G builds the query string and URL-encodes each value for you, so you don't have to escape spaces by hand:

bash
curl -G 'https://your-matomo.example/matomo.php' \
  --data-urlencode 'idsite=1' \
  --data-urlencode 'rec=1' \
  --data-urlencode 'action_name=Order confirmed' \
  --data-urlencode 'url=https://shop.example/checkout/complete' \
  --data-urlencode 'uid=user_123' \
  --data-urlencode 'rand=489634' \
  --data-urlencode 'apiv=1'

rand is just a cache-buster and apiv is the API version (always 1). The same request shape records conversions too: add idgoal for a goal, or the ecommerce parameters (idgoal=0 with ec_id, a revenue, and ec_items) for an order.

You can assemble these query strings by hand, but for anything real use one of Matomo's official clients so you're not hand-rolling URL encoding: the PHP MatomoTracker class, the iOS / tvOS / macOS SDK, and the Android SDK are all maintained by Matomo. If your raw material is web-server access logs rather than live events, log import is the other server-side path, with its own set of gotchas.

Which identity parameter to send

There isn't one true identity field, and the right one depends on what you know about the visitor:

  • uid: a User ID for someone who is logged in. This is Matomo's recommended choice when you have a real account identity; it stitches a user's activity together across devices and browsers.
  • cid: forces a specific visitor ID (a 16-character hex string). Reach for it when you want a backend hit to join the same anonymous browser session, by passing the visitor ID Matomo already assigned in the browser.
  • _id: the unique visitor ID, also 16-character hex. If you leave it out, Matomo still records the hit; only the unique-visitors metric gets a bit less accurate.

So the failure mode is softer than it's often described: an omitted ID doesn't turn every event into an orphan visit, it just makes visitor matching more heuristic. If you do have an identity, prefer uid; only fall back to cid when you're explicitly tying a hit to an existing browser visitor ID.

When you actually need token_auth

Here's the part the docs bury: ordinary tracking needs no token at all. The JavaScript snippet doesn't carry one, and a plain server-side hit doesn't either. token_auth exists to authenticate a small set of trusted overrides: the parameters where Matomo has to take your word for something it would normally measure for itself.

Server-side, the one that bites everyone is cip. Without it, Matomo logs the IP of the machine sending the request, which is your application server. Geolocation then points every visitor at your server's location, and because they all share that one address, visitor matching gets badly skewed. Pass cip with the real client IP and that's fixed. But cip is an override, so it needs token_auth.

Here's the full set, and what each one stands in for:

ParameterNeeds token_auth?What it overrides
cipYesThe visitor's IP address
cdtYes, when the timestamp is more than 24h oldThe hit's date and time
country, region, city, lat, longYesManual geolocation
uaNoThe user-agent string
uid, cid, _idNoVisitor / user identity

ua doesn't need a token, but you'll almost always send cip and ua together server-side, so the token comes along anyway:

text
&cip=203.0.113.45&ua=<real-user-agent>&token_auth=YOUR_WRITE_TOKEN

One Matomo 5 gotcha if you put the token in a URL like this: a token marked Only allow secure requests is ignored when it arrives as a GET query parameter, and the hit fails to authenticate. So either leave that option off for tokens you send in a GET over HTTPS, or, better, send the hit as a POST with the token in the request body, which is exactly what secure-only tokens are for.

For the token itself, Matomo's own advice is to create a dedicated user with only write permission on the relevant site(s) and use that user's token, rather than embedding an admin or Super User token in app code. Admin and Super User tokens do work; they're just a much bigger blast radius if the token leaks.

The cdt cutoff is configurable on self-hosted installs. The default 24-hour window is the [Tracker] setting tracking_requests_require_authentication_when_custom_timestamp_newer_than in config.ini.php, expressed in seconds (default 86400). Widen it and you can back-date hits further without a token, which is exactly why you should think twice before touching it. And if you do backfill old hits, reprocess the archives for those dates afterwards, or the reports for that period won't pick the data up.

Sending many hits at once

If you're flushing a queue of events, don't fire one request each. Bulk tracking is a single POST to matomo.php with a JSON body: an array of query strings, plus one token_auth if any of them use overrides. Order the requests chronologically, oldest first. Matomo stores them as it receives them, so a shuffled queue produces out-of-order visits.

json
{
  "requests": [
    "?idsite=1&rec=1&url=https://shop.example/a&_id=1a2b3c4d5e6f7a8b",
    "?idsite=1&rec=1&url=https://shop.example/b&_id=1a2b3c4d5e6f7a8b"
  ],
  "token_auth": "YOUR_WRITE_TOKEN"
}

"Can I stop people spamming my tracking endpoint?"

Short answer: not with token_auth, and not by hiding the URL. The tracking endpoint is public by design. Every browser running your snippet has to reach matomo.php, so the URL sits in plain view in the Network tab and anyone can copy and replay it. token_auth authenticates overrides; it does not gate ordinary hits.

If you genuinely must control who submits hits, the fix is architectural, not a parameter. Track server-side only, and restrict matomo.php at the web-server or firewall level so it only accepts requests from your application servers' IPs, or put it behind a private network or reverse proxy. Your app then decides what counts as a legitimate event before forwarding it. (Our hardening checklist covers locking the surface down.)

For almost everyone, that's overkill. There's a lighter, built-in option worth knowing first: under Administration → Websites → Manage, Only track visits and actions when the action URL starts with one of the above URLs tells Matomo to drop hits whose url doesn't match your configured site URLs. It isn't authentication (anyone replaying a real URL still gets through), but it quietly discards a lot of junk aimed at domains you don't own. Past that, handle junk after the fact with IP exclusions, spam-referrer filtering, and excluding known bots, rather than bolting authentication onto an endpoint that was built to be open.

Checking it works

  1. Send one test hit and confirm an HTTP 200 with a 1×1 GIF in the body (add send_image=0 and you'll get a 204 instead, handy for health checks).
  2. Open Visitors → Visits Log, or Real-time, and watch for the visit to land within a minute or two.
  3. If overrides aren't sticking, go to Administration → Diagnostics → Tracking Failures. An insufficient token_auth shows up here as a request that wasn't authenticated but should have been.

And if nothing tracks at all on a self-hosted install, and your PHP or web-server logs mention disabled functions, check disable_functions before you start debugging the API itself. The tracker calls ignore_user_abort, so a host that's disabled it can break tracking. Confirm it against the logs rather than assuming, but re-enabling the function is a common fix.

What we'd actually do

Track server-side from the start for anything that doesn't reliably happen in a browser: orders, refunds, app events, webhooks. Use an official SDK rather than hand-built URLs, and a dedicated write-only token for the cip override. Don't try to lock the public endpoint down unless you have a concrete reason to; spend that effort on bot and spam exclusion instead. And keep one tracking path per pageview: _paq or a Tag Manager container, never both.

Martez connects Matomo with Meta Ads and Google Ads so ROAS, CLV, and attribution sit next to your web analytics instead of in a separate spreadsheet. It's in private beta. Join the waitlist if that's relevant.

The browser was never the only place your business happens, and your analytics shouldn't pretend otherwise.