Matomo Tracking API: Server-Side Hits, Mandatory Parameters and the Mistakes That Still Return 200

Contents
Matomo can record data without a line of JavaScript: a single HTTP request to matomo.php can send a page view, an event or a goal. This provides a way to record activity a browser never sees, such as an order confirmed in the back office, a subscription cancelled by phone or a completed batch job.
The property that makes it pleasant to use is also the one that makes it hard to debug. The endpoint answers almost every request the same way: HTTP 200 and an empty image, whether the hit was recorded, silently discarded or counted in the wrong site. Everything below is about telling those three apart without the response saying anything.

What a Hit Needs at a Minimum
Three parameters decide whether anything is recorded at all: idsite, the site the hit belongs to, rec=1, which is the switch that turns a request into a recording, and something to name the hit – a url, an action_name or both. Without rec=1 the request is answered exactly as before and nothing is stored.
Everything else is optional in the sense that the hit survives without it, and rarely optional in the sense that the report stays useful. A hit without a URL appears in the reports without a name, which is the kind of entry that is discovered months later and can no longer be assigned to anything.
The Visitor Id Decides Everything Else
The parameter _id is a visitor identifier of exactly sixteen hexadecimal characters. Not fifteen, not a UUID with its dashes, not the thirty-two characters of an MD5 hash – Matomo silently ignores anything that does not match the format, and silently is the operative word.
What happens then is worse than an error. Without a usable _id, Matomo derives the visitor from IP address and user agent. That works acceptably in a browser and collapses on a server: every hit arrives from the same address with the same client string, so the whole day’s traffic merges into a single visitor with a remarkable number of actions.
A recognisable case of this is a report in which one visitor accounts for most of the events while the visit count stays flat. It is not a bot; it is a batch job without an identifier.
Custom Dimensions Are Only Numbers
Dimensions are sent as dimension1, dimension2 and so on. The number identifies a slot that must already be configured in the Matomo interface, rather than naming the dimension. If that index has not been configured, the request is accepted but the dimension value is discarded.
The second thing to get right is the scope. A dimension of visit scope holds one value per visit and is overwritten by every later hit that carries it; a dimension of action scope holds one value per action. A tariff name in the wrong scope produces either a value that changes with the last page view or one that cannot be used as a segment across the visit.
What Only a Server May Send
Some parameters change data that a request is not normally allowed to set: cip supplies the real visitor IP address, cdt sets a timestamp other than now, and geographic fields set location data. These require token_auth, except that cdt needs it only for timestamps more than 24 hours in the past. If the token is missing where required, Matomo rejects the entire request with HTTP 400.
The token belongs on a server and nowhere else. In a browser it is readable by every visitor, and it does not grant reading rights but writing rights: anyone holding it can write arbitrary hits into the statistics, and there is no way to tell those apart afterwards. Where a page needs to send hits itself, it sends them without those parameters.
With a token, Matomo also accepts older timestamps, as long as they do not fall behind the configured deletion period for raw data. A week of history can therefore be backfilled with cdt; the reports for the affected days then have to be reprocessed.
Why a Hit Disappears Without a Word
| Cause | What is seen |
|---|---|
Missing rec=1 |
Nothing at all, and the request looks completely normal. |
Wrong idsite |
The hits land in the site that has this id; if no such site exists, Matomo answers with HTTP 400. |
_id in the wrong format |
Every hit merges into one visitor, and the visit count stops rising. |
cdt without a token |
Up to 24 hours back, the hit is recorded with the intended time; older timestamps are refused with HTTP 400. |
| Unconfigured dimension | The hit is recorded, the dimension is not. |
The check that ends most of these in a minute is the visits log with the real-time view open while the request is sent. A hit that appears there is recorded; one that does not was discarded, and the list above says where to look. The response never will.
Questions and answers
Where does a server get a valid _id when the same visitor is also tracked in the browser?
Ideally from the browser itself. Matomo’s JavaScript tracker stores the visitor ID in a cookie whose name starts with _pk_id; its value begins with exactly the sixteen hexadecimal characters that _id expects, followed by a dot and further data. If the request that triggers the server-side hit comes from that browser, the ID can be read there and passed along. Browser and server hits then end up with the same visitor.
Where that route does not exist, as with a subscription cancelled by phone, a separate, stable identifier in the right format is enough. A common approach is to take the first sixteen hexadecimal characters of a hash of the customer number; the full thirty-two-character MD5 hash would be discarded. The same person then receives the same ID on every hit, without the customer number itself appearing in Matomo.
Two caveats come with this. Such a hash remains a pseudonym and therefore personal data, and reading the cookie presupposes that the browser tracker was allowed to set one in the first place. Without consent, or with cookieless tracking, there is nothing to read.
Why do server-side hits all show the same location?
Because without cip Matomo uses the address of the request, which is that of the sending server. The location reports then show the data centre in which the batch job runs. The real visitor address can only be passed with cip, and therefore only together with token_auth.