Code HeavenCode Heaven
Developer API

PHP SDK: Install, Configure, and Use

Install the Code Heaven PHP SDK with Composer, configure it with your vendor key, and call validate(), checkUpdate(), download(), and deactivateDomain(). Built-in response caching and offline grace included.

PHP SDK

The PHP SDK wraps the Developer License API so you do not hand-roll cURL, JSON, retries, caching, or offline grace. It exposes one client with four methods — validate(), checkUpdate(), download(), deactivateDomain() — that map directly to the endpoints you have already seen.

Install with Composer

composer require code-heaven/license-sdk

The package targets PHP 7.4+ and ships PSR-4 autoloaded under the CodeHeaven\License namespace. If your plugin bundles its own vendor directory, prefix-scope the SDK (for example with php-scoper) so it does not collide with another plugin's copy of the same library on the customer's site.

Configure with your vendor key

Construct the client once with your vendor key and reuse it. The key stays server-side — never inline it into distributed plugin source.

<?php

use CodeHeaven\License\Client;

$ch = new Client([
    'vendorKey' => getenv('CH_VENDOR_KEY'),   // from env / secrets manager
    'product'   => 'acme-forms-pro',          // default product for all calls
    'baseUrl'   => 'https://api.code-heaven.com/v1', // default; override for sandbox
    'cacheTtl'  => 21600,    // cache validate() results for 6h (seconds)
    'graceDays' => 14,       // keep honouring the last good verdict for 14 days offline
    'timeout'   => 10,       // per-request seconds
]);

validate()

validate() checks a key + domain and returns a typed result. The SDK caches a successful response for cacheTtl, so calling it on every admin page load costs one network request every six hours rather than one per page.

<?php

$result = $ch->validate(
    licenseKey: 'CH-7K2P-9XQ4-LM83',
    domain: 'example.com'        // product taken from client config
);

if ($result->isValid()) {                 // true only when status === "valid"
    acme_register_premium_features();
} else {
    // $result->status() is one of:
    // invalid | expired | domain_not_activated | revoked
    switch ($result->status()) {
        case 'domain_not_activated':
            $ch->activateDomain('CH-7K2P-9XQ4-LM83', 'example.com');
            $result = $ch->validate('CH-7K2P-9XQ4-LM83', 'example.com');
            break;
        case 'expired':
            acme_show_renewal_notice($result->expiresAt());
            break;
        default:
            acme_show_license_notice();
    }
}

// Entitlement details straight off the result object:
$result->seatLimit();     // int, e.g. 3
$result->activations();   // [['domain' => 'example.com', 'activatedAt' => '...']]
$result->expiresAt();     // '2027-06-04T00:00:00Z' | null

isValid() returns true only when the API status is "valid" — it does the "gate on status, not on HTTP 200" check for you.

checkUpdate()

checkUpdate() calls the updates endpoint and caches the answer for a day by default, so you can call it freely from your update hook.

<?php

$update = $ch->checkUpdate(
    licenseKey: 'CH-7K2P-9XQ4-LM83',
    domain: 'example.com',
    installedVersion: ACME_FORMS_VERSION   // e.g. '2.4.1'
);

if ($update->hasUpdate()) {
    $update->latestVersion();   // '2.6.0'
    $update->changelog();       // [['version' => '2.6.0', 'date' => '...', 'notes' => '...'], ...]
    acme_offer_update($update->latestVersion());
}

download()

download() mints the short-lived signed URL and, by default, fetches the package for you, returning a local path. It requests the URL fresh every call, so you never deal with an expired link.

<?php

// Returns a local temp path to the downloaded zip.
$zipPath = $ch->download(
    licenseKey: 'CH-7K2P-9XQ4-LM83',
    domain: 'example.com',
    version: '2.6.0'
);

// Or, if you only want the signed URL and will fetch it yourself:
$signed = $ch->download('CH-7K2P-9XQ4-LM83', 'example.com', '2.6.0', fetch: false);
$signed->url();        // signed package URL (expires in minutes — use immediately)
$signed->expiresAt();  // '2026-06-04T11:45:00Z'

The SDK does not attach your vendor key to the signed-URL fetch — the URL carries its own signature, exactly as described in Updates and Downloads.

deactivateDomain()

Release a seat when a customer retires or migrates a site. Wire it into your plugin's deactivation hook.

<?php

$ok = $ch->deactivateDomain(
    licenseKey: 'CH-7K2P-9XQ4-LM83',
    domain: 'staging.example.com'
);   // true on { "deactivated": true }

register_deactivation_hook(__FILE__, function () use ($ch) {
    $key    = get_option('acme_license_key', '');
    $domain = parse_url(home_url(), PHP_URL_HOST);
    if ($key) {
        $ch->deactivateDomain($key, $domain);
    }
});

Caching

validate() and checkUpdate() results are cached so you stay under the rate limit and your customers' admin screens stay fast.

  • Default store: in-process for the request, plus a file cache between requests.
  • Pluggable: pass any PSR-16 (SimpleCache) implementation to use Redis, the WP object cache, or transients:
<?php

$ch = new Client([
    'vendorKey' => getenv('CH_VENDOR_KEY'),
    'product'   => 'acme-forms-pro',
    'cache'     => new WordPressTransientCache(), // your PSR-16 adapter
    'cacheTtl'  => 21600,
]);

A successful validate() overwrites the cached verdict. A failed network call does not evict it — that cached verdict is what powers offline grace.

Offline grace

License servers, networks, and DNS all fail sometimes. The SDK never punishes a paying customer for your downtime.

When a validate() call cannot reach the API (timeout, DNS failure, 5xx), the SDK:

  1. Returns the last good cached verdict instead of throwing.
  2. Keeps honouring that verdict for graceDays (14 by default), counting from the last successful check.
  3. Flags the result as served from grace so you can decide how loudly to warn.
<?php

$result = $ch->validate('CH-7K2P-9XQ4-LM83', 'example.com');

if ($result->isStale()) {
    // Served from offline grace — the API was unreachable.
    // Features stay unlocked; optionally show a soft "couldn't reach the
    // license server, retrying later" notice.
    acme_log('License served from offline grace, expires ' . $result->graceExpiresAt());
}

$unlocked = $result->isValid(); // still true throughout the grace window

Once graceDays elapses with no successful check, isValid() returns false and the customer is prompted to reconnect — a fair backstop against indefinite offline use. A single successful validate() resets the window.

Design rule: fail open during transient outages (honour the cached verdict), and fail closed only after grace truly expires. Never hard-lock a paying customer on the first network hiccup, and never unlock indefinitely on no signal.

Sandbox vs. production

Point baseUrl at the same production host for both; the environment is decided by which key you pass. A ch_vendor_test_ key operates on test licenses with no billing effect; a ch_vendor_live_ key operates on real licenses. Run your test suite with a test key, ship with a live key.

That is the whole SDK. For the raw HTTP contract behind each method, see Quickstart, License Lifecycle, and Updates and Downloads.