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 a200response. A200means the API answered; the answer might beexpiredorrevoked. Always inspectstatus.
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:
- Handle activations and seats so customers can move between sites.
- Wire up update checks and downloads.
- Replace the hand-rolled cURL with the PHP SDK, which adds caching and offline grace for free.