Licensing a Marketplace Product
Tell the marketplace how to ask your own server whether an installation holds your product.
Overview
WISECP publishes no licence protocol for the products you sell. You choose the address, what travels with the question, and what a yes looks like.
Entitlement is not the panel's to assert, so it asks you, about one thing: the domain of the installation that is downloading.
Declaring a check is optional. A paid listing without one publishes, and its download stays open at the same bar as a free product.
Prerequisites
Structure
Turn on Declare a licence check, then pick which kind. The two are not interchangeable settings; they are two different integrations, and each asks for its own fields.
| Mode | Who it fits | What you supply |
|---|---|---|
| WISECP | You run WISECP yourself and sell the product as software with a licence key | The check address your own build already embeds |
| Custom | Anything else: your own API, a licence service, a static allow list | The request and what a positive answer looks like |
The panel asks at two moments, never on a page view: a buyer starting a download, and a customer beginning a review. A positive answer is remembered for ten minutes.
Walkthrough
Wire the WISECP mode
- Open your software product in your own panel and go to its Licensing tab. The check address is shown there, ready to copy:
https://your-site.example/license/checking/{token}/{product-id}. - Paste it into Check address. Nothing else is asked: the request and the answer are both known.
- The panel calls it with the buyer's domain. Your installation answers with the plain word
OK, or with the newer signed envelope. Both are read as a yes.
That address is the controller path, not the translated route visitors see. The route name changes with the language; this one does not.
Wire a custom endpoint
- Enter the address and choose POST or GET.
- Name the field the domain travels in. Leave it empty and
domainis used. - Add any constant fields your endpoint needs, one
name: valueper line, and the same for request headers. - Describe a yes. Give the status you expect, the body type, the path into it, and the value that means licensed.
Test it
- Enter a domain that really holds a licence on your server.
- Click Test connection. The call is made from the panel, not your browser, so your credentials never travel to the page.
- A pass stamps the declaration and completes the section. Editing any part of the declaration drops that stamp, so test again after a change.
Check the licence inside your product
The declaration above is the download gate. It says nothing about the copy already installed, so the product enforces itself.
- Ask your own server from inside your code.
License::remote_check()sends the question and remembers the answer for a day, so a licensed product does not phone home on every page view. - Read the verdict yourself. It does not judge the answer or verify a signature;
nullmeans the server could not be reached, not that the copy is unlicensed. - Put the decision in a file you marked for encryption, so the line cannot be deleted. Marking and packaging: Licensing a Module.
Reference
The fields of a custom declaration.
domain.name: value per line.data.valid. JSON with no path is a refusal.0, false, null, no and an empty value are refusals.What a failed check reports back to you.
| Reason | What it means |
|---|---|
| Unreachable | No answer arrived. A declared check that cannot be reached refuses. |
| Status | Your server answered, with a status other than the expected one. |
| Refused | Your server answered, and the answer did not match what you described. |
| Bad address | WISECP mode. The token belongs to another product, or the address carries no token. |
| Bad request | WISECP mode. Your endpoint says required fields did not arrive, so something in front of it dropped them. |
Example
A JSON endpoint that answers 200 with a nested verdict.
{"data": {"valid": true, "expires_at": "2027-06-01"}}
Address = https://vendor.example/api/licence
Method = POST
Domain field = domain
Extra fields = product: sample-pro
Headers = Authorization: Bearer <your token>
Expected status = 200
Body type = JSON
Path = data.valid
Expected value = true
The same server can answer in plain text instead. Then the body type is text, the path stays empty, and the expected value is the word itself, such as OK.
Inside the product, the same question is asked with the carrier. The answer is the raw response, so your code decides what it means.
$answer = \License::remote_check('https://vendor.example/api/licence', [
'product' => 'sample-pro',
'domain' => \Utility::getDomain(),
]);
// Unreachable is not a refusal: do not lock a customer out over your own downtime.
if ($answer === null) return true;
$body = \Utility::jdecode((string) ($answer['body'] ?? ''), true);
return (int) ($answer['status'] ?? 0) === 200 && ($body['data']['valid'] ?? false) === true;
Pitfalls
Once a check is declared, an unreachable server refuses the download. You said "ask me", and no answer is not a yes.
This call is made from the panel to your server, and the customer who would gain from a forged yes is not on that path. A signature in the answer is not checked.
A stamp says "the declaration stored right now answers". Change a header, a path or the address, and the section returns to incomplete until you test again.
Related Articles
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.