Pure PHP SDK

The core package: zero dependencies, compatible with PHP ≥ 7.4 (shared hosting, legacy projects and CMSs included). It is the foundation of the Laravel and Symfony bridges, and the tool for "unblockable" tracking on any PHP site.

Installation

composer require quiet-metrics/php-metrics

Usage

use QuietMetrics\Client;

$qm = new Client('qm_pub_XXXXXXXXXXXXXXXXX', 'qm_sec_XXXXXXXXXXXXXXXX');

$qm->pageview();                          // context inferred from the current request
$qm->event('purchase', ['amount' => 49]); // custom event

The context (URL, referrer, IP, User-Agent, language) is inferred from the current request's superglobals, and every field can be overridden:

$qm->pageview(['url' => 'https://mysite.com/thank-you', 'referrer' => null]);

Constructor options

$qm = new Client('qm_pub_…', 'qm_sec_…', [
    'endpoint' => 'https://quietmetrics.dev/api/v1/collect', // default
    'timeout_ms' => 400,            // total send budget (min 50 ms)
    'async' => true,                // fire-and-forget socket; false = short cURL
    'trust_proxy_headers' => false, // read X-Forwarded-For / -Proto (reverse proxy)
    'defaults' => [],               // fields merged into every hit
]);

defaults comes in handy for multi-site setups: instantiate one client per site with its key, or set a common fallback language/URL.

With the secret key, every send carries the X-QM-Timestamp and X-QM-Signature headers (HMAC-SHA256 of "{timestamp}.{body}"). Only a valid signature allows the service to take into account the visitor's IP and browser passed in the payload; without it, your server itself would be counted as the sole unique visitor.

Two points to watch:

  • Server clock. The signature is rejected beyond ±5 minutes of drift (anti-replay). An NTP-synchronized server never has to think about it; a container with a frozen clock does.
  • The secret key must never appear browser-side or in a public repository.

From the CLI, a cron, a worker

Outside an HTTP request, there is no current URL or IP: without an explicit url, the send is silently dropped. Pass the context:

$qm->event('invoice-generated', ['amount' => 99], [
    'url' => 'https://mysite.com/invoices',
    'ts' => time(),
]);

Shared hosting

The send first attempts an outbound socket (fsockopen, fire-and-forget, ~1 ms perceived). If your host disables it, it automatically falls back to cURL with a short request. If both are unavailable, the send is silently dropped: your site never breaks.

First-party proxy for the script

The examples/qm-proxy.php file relays browser hits through your domain, so domain-based blocklists only see your site:

  1. Drop qm-proxy.php and a copy of qm.js at the site root.
  2. Fill in the QM_ENDPOINT and QM_SECRET constants in the file header (with the secret key, the visitor's IP and browser are taken into account).
  3. Point the script at it:
<script defer src="/qm.js" data-site="qm_pub_…" data-endpoint="/qm-proxy.php"></script>

CSP-wise, script-src 'self' and connect-src 'self' are enough: no third-party domain left browser-side.

Payload limits

JSON payload ≤ 4 KB, event name ≤ 120 characters, ≤ 30 scalar properties (values truncated at 190 characters). Beyond that, the send is dropped or truncated service-side, never a visible error.

Robustness contract

Never breaks the host site: non-blocking send, automatic fallbacks, and any failure (network, DNS, platform unavailable) is silent by contract.

Can't find your answer?

Support replies on business days.