Auto-Updates

LicenceForge integrates with the WordPress plugin and theme update system to deliver new versions to licensed users. Updates are only shown when the licence is active, the server version is newer, and the download package passes SHA-256 hash verification.

How it works

The WPLF_Client_Updater class (part of the 3-class system) hooks into WordPress core to inject update information for your product. The process runs automatically whenever WordPress checks for updates.

WordPress hooks

The updater registers two filters for plugin updates:

Filter Purpose
pre_set_site_transient_update_plugins Injects update data into the WordPress update transient when a newer version is available on the LicenceForge server.
plugins_api Provides detailed plugin information (changelog, banners, icons, compatibility data) for the update detail modal shown when a user clicks "View version details".

Theme hooks

The WPLF_Licensing drop-in class also supports theme updates via the equivalent theme filters:

Filter Purpose
pre_set_site_transient_update_themes Injects update data into the WordPress theme update transient.
themes_api Provides detailed theme information for the update detail modal.

Note

Theme update support is available through the WPLF_Licensing drop-in class. The 3-class WPLF_Client_Updater handles plugin updates only.

Update conditions

The updater only injects update information into the WordPress transient when all of the following conditions are met:

  1. The licence is active (validated and not expired, suspended, or cancelled).
  2. The server version is greater than the currently installed version (compared using version_compare()).
  3. The site is within the rollout percentage for the release (see Rollout awareness below).

If the licence is inactive, the updater does not show an available update. Instead, it displays an "Licence: Inactive" badge on the plugins list to alert the site administrator.

Update response data

When the client queries the LicenceForge server for update information, the server returns a JSON object containing the following fields:

{
  "new_version": "2.5.0",
  "package": "https://licences.example.com/wplf/v1/downloads/my-plugin?token=eyJ0eXAi...",
  "package_hash": "sha256:a3f8c2d1e4b5a6f7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1",
  "tested": "6.7",
  "requires": "6.2",
  "requires_php": "7.4",
  "changelog": "<h4>2.5.0</h4><ul><li>Added conditional logic builder</li><li>Fixed form submission race condition</li></ul>",
  "icons": {
    "1x": "https://licences.example.com/assets/icon-128x128.png",
    "2x": "https://licences.example.com/assets/icon-256x256.png"
  },
  "banners": {
    "low": "https://licences.example.com/assets/banner-772x250.png",
    "high": "https://licences.example.com/assets/banner-1544x500.png"
  },
  "rollout_percentage": 100
}

Package download

The package URL includes a signed token that authorises the download. This token has a default expiry of 5 minutes from the time the update response is generated. If the download does not begin within this window, WordPress will receive a 403 response and the update will fail gracefully.

Warning

Do not cache or redistribute package URLs. Each URL contains a single-use signed token that expires quickly. The server generates a fresh token on every update check.

SHA-256 hash verification

Every package download is verified against a SHA-256 hash to ensure the file has not been tampered with in transit. This is a critical security measure.

Verification flow

  1. The update response includes a package_hash field containing the expected SHA-256 hash, prefixed with sha256:.
  2. After WordPress downloads the package, WPLF_Client_Updater intercepts the response via the http_response filter.
  3. The updater computes the SHA-256 hash of the downloaded file.
  4. If the computed hash matches the expected hash, the update proceeds normally.
  5. If the hashes do not match, the update is aborted and the downloaded file is discarded.

Danger

A hash mismatch indicates the downloaded file differs from what the server intended to deliver. This could be caused by a corrupted download, a CDN issue, or a man-in-the-middle attack. The update is rejected to protect the site.

Rollout awareness

The updater respects the rollout_percentage value from the server response. When a product release is configured with a staggered rollout (e.g. 25%), only a subset of sites will see the update as available.

The client determines eligibility by hashing the site URL and checking whether the resulting value falls within the rollout window. This produces a deterministic, evenly distributed result -- the same site will consistently either see or not see the update for a given rollout percentage.

Once the rollout percentage reaches 100, all licensed sites will see the update. See Rollouts for server-side configuration.

Inactive licence badge

When the licence is not active, the updater adds a red "Licence: Inactive" badge to the plugin's row on the Plugins page. This serves as a visible reminder to the site administrator without interfering with the plugin's normal operation.

The badge is rendered via the after_plugin_row action and styled inline. It does not prevent the plugin from functioning -- it only indicates that automatic updates are unavailable until the licence is reactivated.

WordPress Plugins list showing licence status badge
The inactive licence badge on the plugins list page.

Initialization

The updater requires no configuration beyond a WPLF_Client instance. It reads the product slug, API URL, and plugin file path from the client.

3-class system

require_once __DIR__ . '/includes/class-wplf-client.php';
require_once __DIR__ . '/includes/class-wplf-client-updater.php';

$client  = new WPLF_Client( 'my-plugin', 'https://licences.example.com', __FILE__ );
$updater = new WPLF_Client_Updater( $client );

Drop-in system

With the WPLF_Licensing drop-in, auto-updates are enabled by default. No additional initialization is required -- the drop-in handles both plugin and theme updates automatically when a licence is registered.

require_once __DIR__ . '/includes/class-wplf-licensing.php';

WPLF_Licensing::register( [
    'product_slug' => 'my-plugin',
    'api_url'      => 'https://licences.example.com',
    'plugin_file'  => __FILE__,
] );

Next steps