Hits without matomo.js
A page that tracks on the server and loads no matomo.js still has things only the browser
sees: a click on a button, an outlink or a download, a search that runs in the page, the time a
reader keeps a page open. The facade can record all of them (event(), outlink(),
download(), siteSearch(), ping()), but something has to carry them from the browser to the
server. The hit beacon is that route, so you do not build one of your own.
Turn it on
// config/matomo-analytics.php
'hit_beacon' => [
'enabled' => true,
],
Put the directive next to your tracking snippet:
@matomoHitBeacon
It defines window.matomoHit(type, data), and every call sends one hit to the route. Pass your
CSP nonce as the argument (@matomoHitBeacon($nonce)) if your policy needs one.
What a page can send
type | Fields | Recorded as |
|---|---|---|
event | category, action, optional name and value | Matomo::event() |
outlink | link, the absolute URL that was clicked | Matomo::outlink() |
download | link, the absolute URL of the file | Matomo::download() |
search | keyword, optional category and count | Matomo::siteSearch() |
ping | none | Matomo::ping(), the heartbeat that measures time on the page |
matomoHit('event', { category: 'Docs', action: 'copy', name: 'install command' });
matomoHit('outlink', { link: 'https://github.com/pushery/matomo-analytics-for-laravel' });
matomoHit('download', { link: 'https://example.com/files/guide.pdf' });
matomoHit('search', { keyword: 'queue', count: 3 });
setInterval(function () { matomoHit('ping'); }, 15000);
A download sent as download lands in Matomo's Downloads report. Sent as an event, it would
leave that report empty.
The helper adds the page's own address as url, and the hit is filed under that page. The route
takes a url from this application's origin only; one from anywhere else is dropped, and the
hit is filed under the request it arrived on.
What the route accepts
The route is public by necessity, so every field is treated as untrusted:
- The request has to come from your own pages. Its
Originheader must be this application's origin, and withoutOriginthe browser'sSec-Fetch-Sitehas to saysame-origin. Another site cannot make its visitors send hits to your Matomo; a client that calls the route directly, with neither header, meets the rate limit. - Text is bounded. A category, action, name, keyword or search category is at most 255
characters, and a link at most 2,048. A link has to be an absolute
httporhttpsURL. - Numbers have to be finite, and a result count a whole number of zero or more.
event_categorieslimits what a page may send. Empty, the default, allows every category, the waymatomo.jsdoes. Name categories to accept only those.- The throttle is
60,1: sixty requests a minute per client address, and per /64 for IPv6. A heartbeat every fifteen seconds uses four of them.
A payload the route will not record is answered with 422, a request from another origin with
403, and the route answers 404 while the feature is off.
Everything recorded goes through the tracking gate, like a hit your code sends: Do-Not-Track, the opt-out cookie, bots and excluded routes all apply, the excluded routes against the page the hit names.
Sessions and CSRF
The route sits outside every middleware group, because sendBeacon() carries no CSRF token.
No session is started there, so tracking.track_authenticated and tracking.except_abilities
see a guest. Name ['web'] in hit_beacon.middleware if you need those two rules, and exempt
the route from CSRF on your side.