Skip to content

Phase 07: Activation & Heartbeat

Implement the device lifecycle: activate a seat, renew its lease via heartbeat, deactivate on demand, and cull zombies that stop checking in.

  • src/Services/ActivationService.php
  • src/Services/HeartbeatService.php

Each license has a max_activations limit (NULL = unlimited). On every activation attempt:

  1. Look up the machine record by fingerprint hash.
  2. Idempotent re-activation — if the fingerprint already maps to an active machine for this license, return the existing machine without consuming another seat. This prevents double-counting on client retries.
  3. Count active machines for the license against max_activations.
  4. Apply the overage strategy:
StrategyBehaviour
denyReject at max_activations
allow_1_25xAllow up to ceil(max × 1.25)
allow_2xAllow up to max × 2

Machines get a lease_expires_at timestamp set to now + heartbeat_interval. The interval is filterable:

$interval = apply_filters('wplm_heartbeat_interval', 600); // seconds

A machine is considered “alive” while lease_expires_at > now. When a lease expires the seat remains recorded (not auto-freed) — cull_zombies() handles cleanup separately.

public function __construct(
MachineRepository $machine_repo,
LicenseRepository $license_repo,
ActivationLogRepository $log_repo,
Fingerprint $fingerprint,
)

Called by the POST /wplm/v1/heartbeat endpoint. Updates lease_expires_at on the machine row and logs an HEARTBEAT entry in the activation log.

Scheduled WP-Cron job (runs every wplm_zombie_window / 2 seconds, default 600 s). Finds all machines where lease_expires_at < now - zombie_window and marks them status = inactive. The zombie window is filterable:

$zombie_window = apply_filters('wplm_zombie_window', 1200); // seconds

This means: if a client hasn’t sent a heartbeat for 20 minutes (by default), its seat is freed for another device. The two-stage gap (lease expired → zombie cull) prevents seats from being freed during momentary network outages.

Both activate and heartbeat paths write an ActivationLog row with:

  • license_id, machine_id
  • event_typeactivate, deactivate, heartbeat, cull
  • ip_address (VARBINARY 16, INET6_ATON)
  • created_at