If the site you are measuring runs a Content-Security-Policy, it needs two directives. Nothing else has to be relaxed.
script-src https://loghound.example.com; connect-src https://loghound.example.com;
connect-src the beacon fails silently
The script loads, runs and measures — and the browser blocks the POST. There is no error anywhere you would look: the page is fine, the console shows a violation nobody is watching, and the panel simply shows nothing for that host. This is the commonest way a beacon install reports nothing.
Both directives are needed even though the beacon prefers navigator.sendBeacon, because a browser that lacks it or refuses the call falls back to fetch, and connect-src governs both.
To confirm, open the measured page with the browser console showing and look for a CSP violation naming your Loghound host. Then check the panel: a host that is sending nothing at all will have no sessions on the Virtual hosts view.
The beacon uses no eval, no new Function, no inline handlers and no innerHTML. That is also why its User-Agent claim probes test for functions rather than for syntax features — detecting optional chaining or class fields would require eval, and requiring a site to relax its policy in order to be measured would be a poor trade.
- No
style-srcchange: the beacon adds nothing to the page. - No
img-srcchange: there is no tracking pixel. - No
frame-srcchange: nothing is embedded. - No third-party origin at any point. The beacon talks to your Loghound host and to nothing else.
There is no preflight, and that is by design rather than by luck. The beacon sends Content-Type: text/plain;charset=UTF-8 with no custom headers, which makes the POST a CORS-simple request — byte-identical to what navigator.sendBeacon sends. An OPTIONS should therefore never happen; the collector answers one anyway, because a proxy or a policy on the measured site can turn a simple request into a preflight and answering costs nothing.
Response headers on every reply:
Access-Control-Allow-Origin: * Access-Control-Allow-Methods: POST, OPTIONS Access-Control-Allow-Headers: Content-Type Access-Control-Expose-Headers: X-LH-S, X-LH-T Access-Control-Max-Age: 86400
Access-Control-Expose-Headers is the load-bearing one
The collector answers 204 with no body, always, so that it can never be used as an oracle for whether a token or a session id is valid. A bodiless response leaves exactly one channel for the session id and its token: response headers. Without that header the page’s JavaScript cannot read them, the first call succeeds, the beacon gets no credentials, and every later payload is silently dropped.
credentials: 'omit' is set explicitly, which matters most when Loghound is hosted on the same domain as the site being measured: the browser’s default would otherwise attach that site’s cookies to every beacon. The wildcard origin is safe here because the endpoint takes no credentials, has no cookies to send, returns no body and performs no action on behalf of a signed-in user — and because the token is bound to the origin it was issued to.