Comment vérifier une clé de licence à l’intérieur de votre produit : la bibliothèque, l’API, des intégrations prêtes pour onze environnements, et les règles qui gardent les sites de vos acheteurs en marche.
Une clé de licence lie votre produit à un domaine, ou à autant de domaines que l’acheteur en a payés. Elle permet à un client honnête de rester dans ce qu’il a acheté, elle vous donne le compte de vos installations, et elle rend une clé fuitée tuable — son propriétaire la régénère et celle qui a fuité meurt. Ce n’est pas une protection contre la copie. Votre acheteur a vos sources et peut retirer le contrôle en cinq minutes. Quiconque prétend le contraire vous vend quelque chose.
Le fichier que vous téléchargez est généré pour un produit. La classe, le nom de fichier et le fichier de cache portent tous un nom dérivé de ce produit, et l’adresse appelée est assemblée à l’exécution plutôt qu’écrite d’un bloc. Deux de vos propres produits ne partagent pas un seul identifiant.
Soyons clairs sur ce que cela vous achète. Qui ouvre vos fichiers et les lit trouve le contrôle en deux minutes — cela n’a pas changé et ne changera jamais. Ce que cela arrête, c’est l’autre chose : un script qui cherche un nom de classe connu dans des milliers de produits et le retire tout seul. C’est ainsi que les copies nullées se fabriquent en série, et cela ne marche plus ici. Le coût du piratage de votre produit passe de rien à un examen manuel, produit par produit.
Les exemples de cette page appellent la classe Licence. Dans votre téléchargement, elle porte son vrai nom, affiché à côté du bouton de téléchargement dans votre espace auteur.
Ouvrez celui qui correspond à votre produit. Chaque bloc est complet et prêt à coller ; renommez monmodule et adaptez le rangement de la clé à vos propres réglages. Les environnements PHP utilisent la bibliothèque à télécharger ; les autres montrent le client complet, cache et vérification de signature compris.
La forme la plus courte. Placez le contrôle à l’entrée de ce que vous protégez — un contrôleur frontal, une page d’administration, un script planifié.
require_once __DIR__ . '/Licence.php';
$licence = new Licence(
$config['licence'],
__DIR__ . '/cache'
);
if (!$licence->valide()) {
http_response_code(403);
exit($licence->message());
}
Gardez une seule instance pour tout le module. Contrôlez dans getContent() pour que le marchand voie pourquoi l’écran de configuration refuse, et dans vos hooks d’affichage pour qu’un module resté à moitié configuré ne s’affiche pas en boutique.
require_once dirname(__FILE__) . '/Licence.php';
class MonModule extends Module
{
private $licence = null;
private function licence()
{
if ($this->licence === null) {
$this->licence = new Licence(
Configuration::get('MONMODULE_LICENCE'),
_PS_CACHE_DIR_ . 'monmodule'
);
}
return $this->licence;
}
public function getContent()
{
if (Tools::isSubmit('submitLicence')) {
Configuration::updateValue('MONMODULE_LICENCE', Tools::getValue('licence'));
}
$l = $this->licence();
if (!$l->valide()) {
return $this->displayError($l->message()) . $this->formulaireLicence();
}
if ($l->essai()) {
return $this->displayWarning($this->l('Development licence')) . $this->contenu();
}
return $this->contenu();
}
public function hookDisplayHeader()
{
if (!$this->licence()->valide()) {
return '';
}
return $this->contenuFront();
}
}
Vérifié sur 1.7, 8 et 9. Les appels utilisés — Module, Configuration, Tools, _PS_CACHE_DIR_ — ont traversé les suppressions de la 9.0 sans être touchés. Les dépréciations annoncées pour la 10.0 concernent les contrôleurs d’administration, que cet exemple n’utilise pas.
Une instance statique dans une fonction, un avis dans l’administration, et le corps de l’extension derrière le contrôle. Ne faites pas le contrôle sur le front de chaque page — le cache le rend peu coûteux, mais un avis a sa place dans l’administration.
require_once __DIR__ . '/Licence.php';
function monplug_licence() {
static $l = null;
if ($l === null) {
$l = new Licence(
get_option('monplug_licence'),
WP_CONTENT_DIR . '/cache/monplug'
);
}
return $l;
}
add_action('admin_notices', function () {
$l = monplug_licence();
if (!$l->valide()) {
printf('<div class="notice notice-error"><p>%s</p></div>', esc_html($l->message()));
} elseif ($l->essai()) {
echo '<div class="notice notice-warning"><p>'
. esc_html__('Development licence', 'monplug') . '</p></div>';
}
});
add_action('init', function () {
if (monplug_licence()->valide()) {
monplug_demarrer();
}
});
Les cinq appels utilisés — add_action, add_filter, get_option, esc_html, WP_CONTENT_DIR — n’ont pas bougé depuis WordPress 5.0 et fonctionnent toujours sur la branche 7.1. Rien ici ne dépend de l’éditeur de blocs ni d’une route REST.
Pour WooCommerce, conditionnez ce que vous ajoutez à la boutique plutôt que l’extension entière : une passerelle de paiement qui disparaît est plus claire pour le marchand qu’une extension qui meurt en silence.
add_filter('woocommerce_payment_gateways', function ($passerelles) {
if (monplug_licence()->valide()) {
$passerelles[] = 'WC_Gateway_MonPlug';
}
return $passerelles;
});
add_action('woocommerce_admin_field_monplug_licence', function () {
$l = monplug_licence();
if (!$l->valide()) {
echo '<div class="error inline"><p>' . esc_html($l->message()) . '</p></div>';
}
});
Enveloppez la bibliothèque dans un modèle et injectez-la où vous en avez besoin. Le dossier de cache doit être celui de Magento, sinon un déploiement effacera votre verdict à chaque mise en production.
namespace Editeur\MonModule\Model;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Filesystem\DirectoryList;
class Licence
{
private $licence;
public function __construct(ScopeConfigInterface $config, DirectoryList $dossiers)
{
$this->licence = new \Licence(
(string) $config->getValue('monmodule/general/licence'),
$dossiers->getPath(DirectoryList::CACHE) . '/monmodule'
);
}
public function valide(): bool
{
return $this->licence->valide();
}
public function message(): string
{
return $this->licence->message();
}
}
La bibliothèque n’a pas d’espace de noms : déclarez-la dans le composer.json de votre module plutôt que d’appeler require_once à la main.
{
"autoload": {
"files": [
"Licence.php"
]
}
}
Un middleware est la bonne place : enregistrez-le sur le groupe de routes qui appartient à votre produit, jamais globalement, pour qu’un souci de licence n’emporte jamais le reste de l’application.
namespace App\Licence;
use Closure;
use Illuminate\Http\Request;
class VerifieLicence
{
private $licence;
public function __construct()
{
$this->licence = new \Licence(
config('monmodule.licence'),
storage_path('app/monmodule')
);
}
public function handle(Request $requete, Closure $suivant)
{
if (!$this->licence->valide()) {
abort(403, $this->licence->message());
}
return $suivant($requete);
}
}
Un abonné d’événement sur kernel.request, qui ignore les sous-requêtes. Passez la clé et le dossier de cache en arguments dans votre définition de service plutôt que de lire la configuration dans la classe.
namespace App\Licence;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
class LicenceSubscriber implements EventSubscriberInterface
{
private $licence;
public function __construct(string $cle, string $dossierCache)
{
$this->licence = new \Licence($cle, $dossierCache);
}
public function onKernelRequest(RequestEvent $evenement): void
{
if (!$evenement->isMainRequest()) {
return;
}
if (!$this->licence->valide()) {
throw new AccessDeniedHttpException($this->licence->message());
}
}
public static function getSubscribedEvents(): array
{
return [KernelEvents::REQUEST => ['onKernelRequest', 16]];
}
}
isMainRequest() est arrivée dans Symfony 5.3. Sur une version antérieure, appelez isMasterRequest() à la place — tout le reste est identique.
Aucune dépendance au-delà de la bibliothèque standard. Voici le client complet : réponse signée, cache lié à son hôte, réutilisation 24 heures et sursis de 30 jours — les mêmes règles que la bibliothèque PHP.
import { createPublicKey, verify } from 'node:crypto';
import { readFile, writeFile, mkdir } from 'node:fs/promises';
import { dirname } from 'node:path';
const API = 'https://addonsmarket.com/api/licence';
const PUBLIQUE = '/PTkKQpweo9XCLOS0+b0vgrecx7Ly/SsAuwTiA737Tw=';
const SURSIS = 2592000;
const publique = createPublicKey({
key: Buffer.concat([Buffer.from('302a300506032b6570032100', 'hex'),
Buffer.from(PUBLIQUE, 'base64')]),
format: 'der',
type: 'spki',
});
const canonique = (d) =>
JSON.stringify(Object.fromEntries(Object.keys(d).sort().map((k) => [k, d[k]])));
const signee = (rep, hote) =>
rep && rep.donnees && rep.signature
&& rep.donnees.hote === hote
&& verify(null, Buffer.from(canonique(rep.donnees), 'utf8'), publique,
Buffer.from(rep.signature, 'base64'));
export async function etat(cle, hote, fichierCache) {
const maintenant = Math.floor(Date.now() / 1000);
let cache = null;
try {
const lu = JSON.parse(await readFile(fichierCache, 'utf8'));
if (signee(lu, hote)) cache = lu;
} catch {}
if (cache && cache.donnees.revalider > maintenant) return cache.donnees;
try {
const r = await fetch(API, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'User-Agent': 'MonProduit/1.0' },
body: JSON.stringify({ cle, url: hote }),
signal: AbortSignal.timeout(6000),
});
if (!r.ok) throw new Error(String(r.status));
const rep = await r.json();
if (!signee(rep, hote)) throw new Error('signature');
await mkdir(dirname(fichierCache), { recursive: true });
await writeFile(fichierCache, JSON.stringify(rep));
return rep.donnees;
} catch {
if (cache && cache.donnees.emis + SURSIS > maintenant) return cache.donnees;
return { ok: false, motif: 'reseau' };
}
}
Node 18 ou plus récent : fetch est devenu global en 18, et AbortSignal.timeout en 17.3. Sur une version antérieure, remplacez ces deux-là par https.request et votre propre minuteur.
Appelez-le ensuite une fois par requête, et ne bloquez jamais sur une panne réseau.
import { etat } from './licence.mjs';
const d = await etat(process.env.LICENCE, req.hostname, './var/licence.json');
if (!d.ok && d.motif !== 'reseau') {
return res.status(403).json({ erreur: d.motif });
}
Il n’existe pas de façon honnête de vérifier une licence dans un navigateur. Ce que vous écrivez tourne sur la machine de l’acheteur, dans du code qu’il peut lire, et le retirer prend une ligne dans les outils de développement. Nous ne fournissons pas de bibliothèque front parce que ce serait vous vendre un placebo.
Si votre produit a un serveur, contrôlez là-bas et exposez le verdict comme une donnée. L’interface peut alors dire quelque chose d’utile sans que le contrôle lui-même soit dans le navigateur.
app.get('/api/etat', async (req, res) => {
const d = await etat(process.env.LICENCE, req.hostname, './var/licence.json');
res.json({ actif: d.ok, essai: d.essai });
});
const [actif, setActif] = useState(null);
useEffect(() => {
fetch('/api/etat')
.then((r) => r.json())
.then((d) => setActif(d.actif));
}, []);
if (actif === false) {
return <Bandeau>Licence inactive</Bandeau>;
}
Si votre produit est un gabarit front sans aucun serveur, il n’y a rien à contrôler à l’exécution et il ne faut pas faire semblant. La licence s’applique là où elle le peut : au téléchargement, et par les conditions que votre acheteur a acceptées.
Une application Shopify a son propre serveur : utilisez-y le client Node ou PHP, et envoyez le domaine de la boutique — l’adresse myshopify.com, ou le domaine personnalisé si c’est sur lui que vous vous appuyez. Un thème Shopify n’a pas de serveur : c’est la section front ci-dessus qui s’applique.
Bibliothèque standard, plus cryptography pour la signature. Mêmes règles qu’ailleurs : réutilisation 24 heures, conservation du dernier verdict 30 jours si nous nous taisons, blocage seulement sur un refus signé.
import base64, json, time, urllib.request
from pathlib import Path
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
API = 'https://addonsmarket.com/api/licence'
PUBLIQUE = '/PTkKQpweo9XCLOS0+b0vgrecx7Ly/SsAuwTiA737Tw='
SURSIS = 2592000
_publique = Ed25519PublicKey.from_public_bytes(base64.b64decode(PUBLIQUE))
def _canonique(d):
return json.dumps(d, sort_keys=True, separators=(',', ':'), ensure_ascii=False)
def _signee(rep, hote):
try:
if rep['donnees']['hote'] != hote:
return False
_publique.verify(base64.b64decode(rep['signature']),
_canonique(rep['donnees']).encode('utf-8'))
return True
except (KeyError, TypeError, ValueError, InvalidSignature):
return False
def etat(cle, hote, fichier_cache):
maintenant = int(time.time())
chemin = Path(fichier_cache)
cache = None
try:
lu = json.loads(chemin.read_text(encoding='utf-8'))
if _signee(lu, hote):
cache = lu
except (OSError, ValueError):
pass
if cache and cache['donnees']['revalider'] > maintenant:
return cache['donnees']
try:
corps = json.dumps({'cle': cle, 'url': hote}).encode('utf-8')
requete = urllib.request.Request(API, data=corps, headers={
'Content-Type': 'application/json',
'User-Agent': 'MonProduit/1.0',
})
with urllib.request.urlopen(requete, timeout=6) as r:
rep = json.loads(r.read().decode('utf-8'))
if not _signee(rep, hote):
raise ValueError('signature')
chemin.parent.mkdir(parents=True, exist_ok=True)
chemin.write_text(json.dumps(rep), encoding='utf-8')
return rep['donnees']
except Exception:
if cache and cache['donnees']['emis'] + SURSIS > maintenant:
return cache['donnees']
return {'ok': False, 'motif': 'reseau'}
Pour tout le reste — Go, Ruby, .NET, Java, un script shell. Un POST, un corps JSON. Voyez la section API plus bas pour les champs, et la section signature pour ce que vous devez vérifier avant de faire confiance à la réponse.
curl -X POST https://addonsmarket.com/api/licence \
-H 'Content-Type: application/json' \
-H 'User-Agent: MonProduit/1.0' \
-d '{"cle":"VOTRE_CLE","url":"boutique.exemple.fr"}'
Pour un thème ou un script vendu à quelqu’un qui n’est pas développeur, couper le site sur une copie non licenciée est le mauvais geste : la personne qui en souffre est en général l’acheteur qui a mal recopié sa clé, et c’est votre boîte de support qui paie. banniere() renvoie à la place une petite mention fixe — rien d’autre ne change sur la page.
require_once __DIR__ . '/Licence.php';
$reglages = @include __DIR__ . '/licence.php';
$licence = new Licence(
is_array($reglages) ? $reglages['cle'] : '',
__DIR__ . '/cache'
);
register_shutdown_function(function () use ($licence) {
echo $licence->banniere('My theme');
});
L’acheteur la fait disparaître en mettant sa clé dans un fichier, à côté de la bibliothèque. Ce fichier est la seule chose qu’il ait à toucher.
<?php
return array(
'cle' => '',
);
Elle renvoie une chaîne vide quand la licence est valable, et aussi quand le réseau a échoué — un visiteur ne voit jamais de mention parce que notre serveur a eu une mauvaise nuit. Sur une clé de développement elle le dit, discrètement, ce qui est exactement ce qu’il faut pendant que vous construisez.
Un thème HTML, un fichier Figma, un jeu d’icônes, une police : rien de tout cela ne tourne sur un serveur que vous pouvez interroger. Tout ce que vous livrez est lu, modifié et redistribuable par qui détient les fichiers. Il n’y a pas de contrôle à ajouter, et il n’existe aucune version de cette page où cela change.
Ne faites pas semblant. Un script obscurci qui appelle le serveur depuis une page statique, une empreinte cachée dans un canvas, une version qui se casse au bout de trente jours : tout cela se retire en un après-midi par la seule personne qui allait vous pirater, et tout cela finit en ticket de support chez les cinquante qui ont payé. Vous dépenseriez votre crédibilité pour ne rien protéger.
Ce qui existe à la place agit à la livraison, pas à l’exécution. Chaque téléchargement est reconditionné pour l’acheteur qui l’a demandé, et porte trois choses :
C’est de la traçabilité, pas de la prévention, et nous ne vous la vendrons pas pour autre chose : un acheteur qui supprime le fichier et vide le commentaire repart avec une copie propre. Ce qu’elle vous donne, c’est un nom quand une copie apparaît là où elle ne devrait pas, une raison pour cet acheteur d’y réfléchir à deux fois, et les deux choses qu’une copie piratée n’a jamais — les mises à jour et votre support.
Une distinction compte ici. Si votre thème contient du PHP, ne serait-ce qu’un formulaire de contact, un fichier de configuration ou un include, alors il tourne sur un serveur, le contrôle ci-dessus est réel et la mention fonctionne. Seul le cas entièrement statique, du HTML et du CSS et rien d’autre, n’offre aucun contrôle à l’exécution.
Un seul fichier PHP, aucune dépendance, compatible jusqu’à PHP 5.6 pour tourner sur les vieilles installations que vos acheteurs ont encore. Quatre méthodes suffisent.
| new Licence($cle, $dossierCache) | Construisez-la avec la clé de l’acheteur et un dossier inscriptible pour le cache. |
| $licence->valide() | Vrai quand le produit peut fonctionner sur ce domaine. |
| $licence->essai() | Vrai sur une clé de développement ou un domaine de test. Affichez une mention discrète — ne livrez jamais une version qui la masque. |
| $licence->message() | Une phrase à montrer à l’acheteur, déjà traduite, qui nomme les domaines et date l’événement. |
Ne laissez jamais un problème réseau couper la boutique de votre acheteur. La bibliothèque s’en charge déjà et vous ne devez pas la contourner : un verdict frais est réutilisé 24 heures ; quand notre serveur ne répond pas, le dernier verdict connu est conservé 30 jours et le produit continue de fonctionner ; seul un refus signé l’arrête, immédiatement. Si notre serveur a une mauvaise nuit, aucune boutique ne s’éteint. Si une clé est transférée, l’ancien site s’arrête sous 24 heures.
Une licence se lie au domaine enregistrable. Une clé sur exemple.fr couvre www.exemple.fr, boutique.exemple.fr et tout autre sous-domaine — votre acheteur ne vous écrira pas à cause d’une redirection manquante. Les adresses de test ne consomment jamais de licence : localhost, .local, .test, et les préfixes habituels dev., staging., preprod. fonctionnent sans rien consommer.
La bibliothèque n’est qu’une enveloppe autour d’un seul point d’entrée. Appelez-le directement si vous travaillez hors PHP — mais vous devrez alors implémenter vous-même les règles de cache ci-dessus, et vérifier la signature.
POST https://addonsmarket.com/api/licence
POST /api/licence HTTP/1.1
Host: addonsmarket.com
Content-Type: application/json
User-Agent: MonProduit/1.0
{
"cle": "VOTRE_CLE",
"url": "boutique.exemple.fr"
}
| cle | string | La clé de l’acheteur. Lettres et chiffres seulement ; nous retirons le reste, donc les espaces et tirets collés sont sans effet. |
| url | string | L’hôte sur lequel tourne votre produit. Une URL complète fonctionne aussi — nous n’en gardons que l’hôte. Retirez vous-même le port et le www. si vous voulez que la réponse corresponde à ce que vous avez envoyé. |
{
"donnees": {
"ok": true,
"motif": "",
"domaine": "exemple.fr",
"essai": false,
"quand": "",
"emis": 1790687453,
"revalider": 1790773853,
"sursis": 2592000,
"hote": "boutique.exemple.fr"
},
"signature": "4p9JXJRfcB5R/LNEQwr/cwnVv1M5R1OdCNcU…"
}
| ok | bool | Si le produit peut fonctionner ici. |
| motif | string | Pourquoi non, quand ok vaut faux. Voir le tableau plus bas. |
| domaine | string | Le domaine auquel la licence est liée. |
| essai | bool | Clé de développement ou domaine de test. |
| quand | string | Quand la clé a été remplacée ou révoquée. |
| emis | int | Quand nous avons répondu. Le début du sursis de 30 jours. |
| revalider | int | Horodatage avant lequel vous ne devez pas redemander. |
| sursis | int | Secondes pendant lesquelles vous pouvez garder ce verdict si nous ne répondons plus. |
| hote | string | L’hôte sur lequel nous avons répondu. Rejetez la réponse si ce n’est pas le vôtre. |
| 200 | Un verdict signé. Notez qu’un refus est aussi un 200 — lisez ok, pas le code HTTP. |
| 400 | Clé ou hôte manquant. |
| 405 | Autre chose qu’un POST ou un OPTIONS. |
| 429 | Limite d’appels atteinte. Gardez votre verdict en cache et réessayez plus tard — ne traitez jamais ceci comme un refus. |
Trente appels par minute et par adresse IP. Avec les règles de cache respectées, une boutique fait un appel par jour ; vous n’atteindrez donc cette limite que depuis un test de charge ou une boucle qui a oublié son cache.
Envoyez un User-Agent qui nomme votre produit. Quelques agents par défaut de bibliothèques HTTP — celui d’urllib en Python notamment — sont filtrés avant de nous parvenir et reçoivent un 403. N’importe quelle chaîne personnalisée l’évite.
Sans signature, une ligne dans le fichier hosts d’un serveur suffit à répondre « licence valide » à notre place. Chaque réponse porte une signature Ed25519 sur la forme canonique des données — clés triées, JSON compact. Vérifiez-la avant de faire confiance à quoi que ce soit. La bibliothèque s’en charge, y compris sur la copie en cache, elle-même liée à son hôte pour qu’on ne puisse pas la déplacer d’un site à l’autre.
Utile seulement si vous appelez l’API directement. Reconstruisez la forme canonique à partir de l’objet analysé — jamais à partir des octets bruts reçus, qui peuvent différer — puis vérifiez la signature Ed25519 détachée avec notre clé publique.
Ed25519, 32 octets bruts en base64. Elle est la même pour tous les auteurs et tous les produits.
/PTkKQpweo9XCLOS0+b0vgrecx7Ly/SsAuwTiA737Tw=
La signature couvre l’objet donnees sérialisé selon ces quatre règles, et rien d’autre. Manquez-en une et toutes les signatures paraîtront invalides.
Pour la réponse ci-dessus, les octets exactement signés :
{"domaine":"exemple.fr","emis":1790687453,"essai":false,"hote":"boutique.exemple.fr","motif":"","ok":true,"quand":"","revalider":1790773853,"sursis":2592000}
function signature_valide(array $donnees, $signature, $publique)
{
ksort($donnees);
$corps = json_encode($donnees, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return sodium_crypto_sign_verify_detached(
base64_decode($signature, true),
$corps,
base64_decode($publique, true)
);
}
import { createPublicKey, verify } from 'node:crypto';
const publique = createPublicKey({
key: Buffer.concat([Buffer.from('302a300506032b6570032100', 'hex'),
Buffer.from('/PTkKQpweo9XCLOS0+b0vgrecx7Ly/SsAuwTiA737Tw=', 'base64')]),
format: 'der',
type: 'spki',
});
const canonique = (d) =>
JSON.stringify(Object.fromEntries(Object.keys(d).sort().map((k) => [k, d[k]])));
const valide = verify(null, Buffer.from(canonique(rep.donnees), 'utf8'),
publique, Buffer.from(rep.signature, 'base64'));
import base64, json
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
publique = Ed25519PublicKey.from_public_bytes(base64.b64decode('/PTkKQpweo9XCLOS0+b0vgrecx7Ly/SsAuwTiA737Tw='))
canonique = json.dumps(rep['donnees'], sort_keys=True,
separators=(',', ':'), ensure_ascii=False)
publique.verify(base64.b64decode(rep['signature']), canonique.encode('utf-8'))
| autre_domaine | La clé est valide mais appartient à un autre domaine. Dites à l’acheteur de la transférer depuis son compte. |
| transfert | La clé a été déplacée vers un autre site et remplacée. L’acheteur en a une neuve. |
| regeneration | L’acheteur a demandé une nouvelle clé. L’ancienne est morte définitivement. |
| revocation | Nous avons révoqué la clé : abus, fraude, ou litige tranché. |
| expiree | Une clé d’essai à durée limitée qui est arrivée à terme. |
| inconnue | Cette clé n’existe pas. En général une faute de frappe. |
| reseau | Nous n’avons pas pu être joints et aucun verdict en cache ne subsiste. Ne bloquez pas là-dessus. |
Votre espace auteur vous donne une clé de développement pour chacun de vos produits. Elle fonctionne sur n’importe quel domaine, n’ouvre que vos propres produits, et signale toujours essai — une installation qui l’utilise affiche donc une mention de développement et ne peut jamais passer pour une copie vendue.
Pour voir comment votre produit se comporte quand il est refusé, envoyez une clé qui n’existe pas. Vous obtenez un vrai refus signé, identique à ce que donne une clé révoquée — vous pouvez donc vérifier votre écran d’erreur sans rien révoquer.
curl -X POST https://addonsmarket.com/api/licence \
-H 'Content-Type: application/json' \
-H 'User-Agent: MonProduit/1.0' \
-d '{"cle":"cettecleNexistePas000000","url":"boutique.exemple.fr"}'
{"donnees":{"ok":false,"motif":"inconnue", …},"signature":"…"}
Votre produit enverra à notre serveur le domaine sur lequel il tourne. C’est un traitement de données personnelles au sens du droit européen, et votre acheteur doit en être informé. Dites-le dans la description de votre produit et dans votre propre documentation. Nous conservons le domaine, et l’IP pendant douze mois pour arbitrer les litiges ; rien d’autre, et nous n’en vendons rien.
Écrivez-nous depuis votre espace auteur. Une question qu’il a fallu poser signifie en général qu’il manque un paragraphe à cette page.