Skip to main content

LicensePushController

\Benecaster\REST\LicensePushController

Class Free

Inbound receiver for POST /benecaster/v1/license-push. The licence server calls it when a licence-state change happens out of band — an order paid, a subscription upgraded, a plan cancelled, a referral credit applied — and the site should pick the change up sooner than the daily validation cycle. The handler runs a validation cycle immediately and leaves the daily schedule in place.

HMAC is the sole authentication. There is no cookie auth and no nonce: the server signs the raw request body with the site’s push secret and sends the result in X-Benecaster-Signature, and the plugin rejects on mismatch.

The push secret’s only source is a /validate response. The server includes it and LicenseValidationCron stores it on the way past. Connecting a show over OAuth does not carry it, and nothing provisions it at activation.

The body is signed but not read. Re-running the validation cycle is the canonical “re-read state from the server” action and always fetches the freshest state, so a payload schema would only duplicate /validate.

Constructor Dependencies

Type Description
\Benecaster\License\LicenseValidationCron Run immediately on a verified push. The daily wp-cron event is deliberately left scheduled.

Methods

Method Visibility Since Description
handle( WP_REST_Request $request ): WP_REST_Response|WP_Error Public Verifies the signature, collapses repeat deliveries, and runs a validation cycle. Returns 200 {"ok":true} on success, 401 on signature mismatch with no body detail, and 503 not_activated when no push secret is stored.

Notes

A 503 does not mean the site is unlicensed. It means this install has never completed a validation ping — the only event that caches the push secret. A freshly OAuth-connected show is fully activated and still returns 503 until its first /validate lands. The behaviour is correct and is not a bug to fix: the server treats the 503 as a known retriable state, drops the push, and the daily validation self-heals the install.

⚠ **The error code on that response is the literal string not_activated, and renaming it would break the server's handling.** It is narrower than it reads, which is what this note exists to explain — treat the mismatch as documented rather than as a typo.

The reason a push carries is an operator breadcrumb, never an instruction. The receiver is deliberately body-opaque, so a push announcing plan_upgraded answered by a /validate saying cancelled leaves the site cancelled. Nothing in the push can move state on its own.

Delivery is at-least-once and per site token. An install running N shows receives N pushes carrying the same payload; repeats are collapsed so one validation cycle runs, and that cycle already iterates every connected show.