Code HeavenCode Heaven
Developer API

Quickstart: Validate a License in 5 Minutes

Validate a Code Heaven license in five minutes. Copy-paste curl and PHP examples that call POST /licenses/validate and gate your plugin's premium features on status == valid.

Quickstart: Validate a License in 5 Minutes

This page gets you from zero to a working license check. By the end you will have called POST /licenses/validate, read the response, and gated a feature on status == "valid".

You need two things first:

  • A vendor API key (how to get one). Use a ch_vendor_test_ key while developing.
  • A license key to test against — issue a test license from your vendor dashboard, or use one a customer gave you.

Step 1 — Validate with curl

The validate endpoint takes a license key, the domain you are checking, and optionally the product. It returns the license's status and entitlements.

curl https://api.code-heaven.com/v1/licenses/validate \
  -H "X-CH-Vendor-Key: $CH_VENDOR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "licenseKey": "CH-7K2P-9XQ4-LM83",
    "domain": "example.com",
    "product": "acme-forms-pro"
  }'

A valid license responds 200:

{
  "valid": true,
  "status": "valid",
  "product": "acme-forms-pro",
  "expiresAt": "2027-06-04T00:00:00Z",
  "activations": [
    { "domain": "example.com", "activatedAt": "2026-06-04T10:21:00Z" }
  ],
  "seatLimit": 3
}

The two fields you gate on are valid and status. They agree: valid is true exactly when status is "valid". Read status when you want to tell the customer why a check failed.

If the key is real and paid-up but this domain has never been activated, you get valid: false with status: "domain_not_activated" — that is your cue to activate the domain (see the license lifecycle):

{
  "valid": false,
  "status": "domain_not_activated",
  "product": "acme-forms-pro",
  "expiresAt": "2027-06-04T00:00:00Z",
  "activations": [],
  "seatLimit": 3
}

Step 2 — Validate from PHP

Here is the same call in plain PHP using cURL — no framework, no SDK. Drop it into your plugin or your licensing proxy.

<?php

function ch_validate_license(string $licenseKey, string $domain, string $product): array
{
    $payload = json_encode([
        'licenseKey' => $licenseKey,
        'domain'     => $domain,
        'product'    => $product,
    ]);

    $ch = curl_init('https://api.code-heaven.com/v1/licenses/validate');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $payload,
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'X-CH-Vendor-Key: ' . getenv('CH_VENDOR_KEY'),
        ],
    ]);

    $body   = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $errno  = curl_errno($ch);
    curl_close($ch);

    // Network failure: never silently unlock or hard-lock. Fall back to your
    // cached verdict and an offline grace window (see the PHP SDK page).
    if ($errno !== 0) {
        throw new RuntimeException('License server unreachable: ' . $errno);
    }

    $data = json_decode($body, true) ?: [];

    if ($status === 401) {
        // Your problem, not the customer's: bad or missing vendor key.
        throw new RuntimeException('Vendor key rejected (401).');
    }

    return $data;
}

Step 3 — Gate the plugin on status == valid

The whole point is this one decision. Premium features run only when the license validates.

<?php

function ch_premium_unlocked(): bool
{
    $domain = parse_url(home_url(), PHP_URL_HOST); // current site domain

    try {
        $result = ch_validate_license(
            get_option('acme_license_key', ''),
            $domain,
            'acme-forms-pro'
        );
    } catch (RuntimeException $e) {
        // Server unreachable or vendor-key error. Use your cached verdict
        // rather than punishing the customer for your downtime.
        return ch_cached_verdict_or_grace();
    }

    return ($result['status'] ?? '') === 'valid';
}

// Usage in your plugin:
if (ch_premium_unlocked()) {
    acme_register_premium_features();
} else {
    acme_show_license_notice(); // prompt the customer to enter/renew a key
}

Gate on status == "valid", not merely on a 200 response. A 200 means the API answered; the answer might be expired or revoked. Always inspect status.

What to do for each status

| status | Meaning | What your plugin should do | |---|---|---| | valid | Paid, active, this domain is activated | Unlock premium features | | invalid | Key is wrong or does not exist | Show "invalid license key" and prompt re-entry | | expired | Key was valid but the term lapsed | Lock features, prompt renewal | | domain_not_activated | Key is fine but this site has no seat | Activate the domain, then re-validate | | revoked | Key was cancelled (refund, chargeback, abuse) | Lock features, do not retry endlessly |

Next steps

You now have a working gate. From here: