Digital Sovereignty for everyone

Limits and filters

The collect route is public in the REST sense (no capability check), but every request has to pass these checks, in this order, or it is refused with the status shown and nothing is dispatched:

  • Size (413): the body may be at most 8192 bytes.
  • Origin (403): the Origin header, or failing that the Referer, must be this site’s home or site URL host.
  • Rate (429): at most 300 requests per IP per minute. The limit is generous because a club’s wifi can put many devices behind one IP. It is counted in the persistent object cache when there is one, otherwise in transients.
  • Shape (400): the body must be JSON of the form { "nonce": "…", "beacons": { key: payload, ... } }. At most 20 keys are dispatched; keys that are not valid slugs are skipped.
  • Nonce (403, lh_beacon_invalid_nonce): the nonce LH Beacon printed into the page, or fetched since (see Page caching), must be valid for the visitor the login cookie identifies, or for user 0 when logged out. It travels in the body, not the X-WP-Nonce header, so a stale one is refused by LH Beacon and never trips the REST API’s own cookie check.

A request that passes returns 204. Taken together, a beacon has to come from a page this site served to that visitor, carrying a nonce that is still within its lifetime. The checks are layered for defence in depth, but they are still not authorisation: a logged-out visitor’s nonce is the same for everyone and visible in the page source. Listeners must validate every payload and never grant anything on the strength of a beacon.

Logging refusals

Every refusal is written to debug.log as a Warning tagged [lh_beacon], in the standard LH format that LH Debug Log Viewer reads. The entry has event beacon_refused, a reason (too_large, origin, rate_limited, invalid_shape, nonce_missing or nonce_invalid), the site, the Origin and Referer headers and the body size. Nonce refusals add the user ID from the login cookie and the beacon keys; rate-limit refusals add the IP address. Payloads and the nonce itself are never logged. Origin and rate refusals on the nonce route are logged the same way.

This is needed because REST refusals are otherwise invisible. LH WP Die Logger reports nonce failures only when wp_die() runs, which a REST route never does, and WordPress does not announce a missing nonce at all. Logging only happens when WP_DEBUG is on.

Page caching

A WordPress nonce is valid for 12 to 24 hours, and a page carries the nonce it was built with, whether it comes from a full-page cache or has simply been left open for hours, such as a sign-on screen on a tablet at the ground. So that such pages can still send, LH Beacon prints the time the nonce was issued alongside it, and the script fetches a fresh one from GET /wp-json/lh-beacon/v1/nonce once it is older than a quarter of the nonce lifetime (6 hours by default). It checks when a plugin queues a beacon, when the tab becomes visible again and every 30 minutes while it stays open, and only on pages that have something queued, so most page views never make the request.

The nonce route is never cached, answers only same-origin requests (by Origin or Referer, as collect does), shares the beacon rate limit, and creates the nonce for the visitor the login cookie identifies, so it verifies exactly as a nonce printed into the page does. On a site that blocks the REST API for logged-out visitors, it must be allowed whatever the login state, since the script sends no X-WP-Nonce header; on LocalHero, LH Disable REST API for anonymous users whitelists it from 1.4.7.

Keeping the full-page cache under 12 hours still matters: a visitor who leaves an old cached page before the refresh has finished sends the page’s original nonce. On LocalHero the LiteSpeed public cache TTL is 12 hours (43200 seconds) and the private (logged-in) cache 30 minutes. A beacon is also refused when the visitor has logged in or out in another tab since the page loaded, since its nonce belongs to the old login state. A run of nonce_invalid entries means refreshes are failing, or a cache is serving pages older than the nonce lifetime.

Filters

  • lh_beacon_return_max_bytes: largest body accepted (default 8192).
  • lh_beacon_return_max_keys: most keys dispatched per request (default 20).
  • lh_beacon_return_rate_limit: requests per IP per window (default 300; 0 disables the limit). Shared by the collect and nonce routes.
  • lh_beacon_return_rate_window: window in seconds (default 60).
  • lh_beacon_return_allowed_hosts: hosts beacons and nonce requests may come from (default this site’s home and site URL hosts).
  • lh_beacon_return_nonce_refresh_after: age in seconds after which the script fetches a fresh nonce (default a quarter of the nonce lifetime, 21600 with WordPress’s default; at least 60).
  • lh_beacon_require_nonce: return false to stop checking the nonce. For emergencies only, such as a cache serving pages older than the nonce lifetime.
  • lh_beacon_get_user_id: the user ID passed to listeners, after the nonce has been checked against the real cookie user; return 0 to anonymise.
  • lh_beacon_return_plugin_debugging_status: return false to stop logging refusals.
  • lh_beacon_get_client_ip: the IP used for rate limiting (default REMOTE_ADDR); filter it when the site is behind a proxy.
  • lh_beacon_return_script_handle and lh_beacon_return_rest_namespace: the script handle (lh-beacon) and REST namespace (lh-beacon/v1). Changing either breaks consumers that use the defaults.
  • lh_beacon_init_plugin: return false to switch the plugin off.

If you like this plugin please consider making a donation to further development

Install LHero.org

lhero.org

Open it straight from your home screen, full screen and quicker to load.

  1. Tap the Share button . If you cannot see it, tap ••• first.
  2. Scroll down and tap Add to Home Screen.
  3. Tap Add.
  1. Tap the browser menu â‹®.
  2. Tap Install app or Add to Home screen.
  1. In the menu bar, choose File, then Add to Dock.

Use your browser's menu to install this site or add it to your home screen.