Skip to main content

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

EventCarriesFires when
VisitorExcludedstring $reasonThe gate declined a hit.
TrackingQueuedarray $payloadsHits were handed off for delivery.
TrackingSentint $count, int $statusMatomo accepted a delivery.
TrackingFailedThrowable $exceptionA delivery failed permanently, in either mode.
HitsDeadLetteredint $count, int $attemptsA batch was moved to the dead-letter store.
DataSubjectForgottenint $visits, array $deletedA 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.