Licensing a Marketplace Product

26 views Markdown

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

A commercial listing Appears the moment the pricing model asks for money. A free product has nothing to license.
A reachable https address Public and answering directly. Private addresses are refused, and a redirect is never followed.
An answer that turns on a domain The question is always about one domain. A server that cannot decide from one cannot be wired to this.
A domain you know is licensed The test needs a real yes: an address that refuses everything is still reachable.

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.

ModeWho it fitsWhat you supply
WISECPYou run WISECP yourself and sell the product as software with a licence keyThe check address your own build already embeds
CustomAnything else: your own API, a licence service, a static allow listThe 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

  1. 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}.
  2. Paste it into Check address. Nothing else is asked: the request and the answer are both known.
  3. 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.
Copy the address, do not type it

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

  1. Enter the address and choose POST or GET.
  2. Name the field the domain travels in. Leave it empty and domain is used.
  3. Add any constant fields your endpoint needs, one name: value per line, and the same for request headers.
  4. Describe a yes. Give the status you expect, the body type, the path into it, and the value that means licensed.

Test it

  1. Enter a domain that really holds a licence on your server.
  2. Click Test connection. The call is made from the panel, not your browser, so your credentials never travel to the page.
  3. 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.

  1. 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.
  2. Read the verdict yourself. It does not judge the answer or verify a signature; null means the server could not be reached, not that the copy is unlicensed.
  3. 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.

AddressRequired. Redirects are not followed, so paste the final address.
MethodPOST or GET. A GET carries the fields as a query string.
Domain fieldThe name the domain arrives under. Defaults to domain.
Extra fieldsOptional constants sent on every call, one name: value per line.
HeadersOptional request headers, same form. Keep any token you send narrow.
Expected statusA mismatch is a refusal. Use 0 when your endpoint answers 200 either way.
Body typeJSON or plain text. Plain text judges the body itself and asks for no path.
PathA dot separated route into the JSON, such as data.valid. JSON with no path is a refusal.
Expected valueCompared without case. Leave it empty to accept any truthy value; 0, false, null, no and an empty value are refusals.

What a failed check reports back to you.

ReasonWhat it means
UnreachableNo answer arrived. A declared check that cannot be reached refuses.
StatusYour server answered, with a status other than the expected one.
RefusedYour server answered, and the answer did not match what you described.
Bad addressWISECP mode. The token belongs to another product, or the address carries no token.
Bad requestWISECP 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.

what your server answers
{"data": {"valid": true, "expires_at": "2027-06-01"}}
the declaration that matches it
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.

the runtime check, in a file you marked for encryption
$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

Silence is not permission

Once a check is declared, an unreachable server refuses the download. You said "ask me", and no answer is not a yes.

The answer is taken at its word

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.

Editing the declaration drops the proof

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.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.