Events
The package fires Laravel events at every interesting point in a hit's life. All of them
live in MatomoAnalytics\Events, all are final readonly, and all can be switched off in
one place:
'events' => true, // false fires none of them
Switching them off is worth doing only if you fire a very high volume of hits and profiled the dispatcher as a cost. The load simulator does exactly that while measuring.
The events
| Event | Carries | Fires when |
|---|---|---|
VisitorExcluded | string $reason | The gate declined a hit. |
TrackingQueued | array $payloads | Hits were handed off for delivery. |
TrackingSent | int $count, int $status | Matomo accepted a delivery. |
TrackingFailed | Throwable $exception | A delivery failed permanently, in either mode. |
HitsDeadLettered | int $count, int $attempts | A batch was moved to the dead-letter store. |
DataSubjectForgotten | int $visits, array $deleted | A GDPR erasure deleted visits. |
VisitorExcluded
use Illuminate\Support\Facades\Event;
use MatomoAnalytics\Events\VisitorExcluded;
Event::listen(fn (VisitorExcluded $event) => logger()->info("Excluded: {$event->reason}"));
reason is one of the gate's reason strings — disabled, not_configured,
environment, dnt, opted_out, bot, authenticated, ability, ip, route,
gate. See the tracking gate for what each means.
This is the most useful event in practice. Counting the reasons in a local or staging environment answers "why is my traffic not showing up?" without any instrumentation inside the package. See troubleshooting.
TrackingQueued
Carries the raw hit payloads that were handed off. It fires once per request, in every mode, with the whole request's payloads — the dispatch sits ahead of the branch that chooses between the queued job and the buffer, so the mode does not change how often you hear about it.
It described a per-hit event in batch mode until 0.24.0, and it no longer does. A
listener written as a hit counter therefore counts requests: read count($event->payloads)
rather than incrementing by one.
Use it to mirror what you track into another system. Note that a queued hit is not yet a
delivered hit; TrackingSent is the confirmation.
TrackingSent
Carries the number of hits delivered and Matomo's HTTP status. It fires from all three
delivery paths — the inline send in sync mode, the queued job, and a batch flush — so a
listener sees every successful delivery regardless of mode.
TrackingFailed
Carries the exception. It means "this will not be attempted again", not "an attempt
failed" — a queued delivery that has exhausted its retries, or a batch the flusher has
dead-lettered as poison or after batch.max_attempts.
It fires in both delivery modes. Until now it fired only in queue mode, while the
shipped config, the service provider and the 0.24.0 changelog all said otherwise, so a
batch-mode application that wired this event as its alarm — the documented way — had a
listener that could not fire. A released batch stays silent, because it is attempted
again on the next flush.
Individual failed attempts are not events; they go through the throttled alerting instead, which is what keeps a transient timeout from paging anyone.
HitsDeadLettered
Carries the hit count and the number of delivery attempts made. Listen for it if you want
to know the moment tracking data starts accumulating in the dead-letter store, rather than
finding out the next time somebody runs matomo:replay --list.
The attempt count distinguishes the two causes: a low number means Matomo rejected the
batch permanently, a number at batch.max_attempts means transient failures persisted.
DataSubjectForgotten
Carries the number of visits erased and the per-area deletion counts. This is the audit signal for "right to be forgotten" requests — see GDPR requests.
It carries counts, not the erased data. It is an audit trail, not a backup.
Registering listeners
Laravel discovers listeners automatically in a standard application. For an explicit registration:
// in a service provider's boot() method
use Illuminate\Support\Facades\Event;
use MatomoAnalytics\Events\HitsDeadLettered;
Event::listen(function (HitsDeadLettered $event): void {
logger()->warning("Matomo dead-lettered {$event->count} hits after {$event->attempts} attempts.");
});
Keep listeners cheap, or queue them. TrackingQueued and TrackingSent fire on the
delivery path; a slow synchronous listener there gives back the latency the package works
to avoid.
Telescope and Horizon already see this
Nothing needs installing or registering for either, because this package uses the framework's own paths rather than going around them.
Telescope records every Matomo hit through its stock watchers: the delivery goes out on
Laravel's HTTP client, so the client-request watcher captures the URL and the payload, and the
batch/queue modes dispatch an ordinary queued job, which the job watcher records. A
package-specific watcher would be a second view of the same rows.
Horizon groups the queued batches under the tag matomo, and under matomo:site-<id> where
a site id is configured. That is one tag per configured site on purpose — anything per-batch
would mint a new tag on every dispatch, and Horizon indexes tags.
Pulse is the one that would need something from this package: a recorder over
TrackingSent/TrackingFailed and a card to draw it. That card is a Livewire component, so it
would pull Livewire into a package that deliberately depends on focused illuminate/*
components and not on the framework — which is why it is a deliberate open question rather than
an oversight. The events above are public, so an application that wants the card today can write
a recorder against them in a few lines.