Programme de publicateurs
Enregistrez votre module, publiez une version, récupérez le paquet signé et recevez les événements dont votre serveur a besoin.
Nous n'hébergeons pas votre module. Vous vendez et distribuez où vous voulez : votre site, votre boutique, une place de marché, un revendeur. Ce qui reste ici, c'est la vérité sur ce fichier, et c'est cette vérité que l'installateur de l'acheteur vérifie avant d'extraire le moindre octet.
Ce que vous faites, une fois#
- Enregistrez l'artefact dans votre compte, section Publication. Le slug est
réservé ici, et non à votre première version : c'est le nom que votre module
emmène dans chaque installation, il va dans votre
module.jsonet il ne change jamais. Les noms de notre espace (bondry-,core,admin,designer,members,lms) sont refusés. - Générez le jeton de publication. C'est un id
bkp_plus un secret montré une seule fois. Un jeton par artefact, révocable sur-le-champ. Révoquer ne retire pas ce qui est déjà publié : cela empêche la prochaine annonce. - Indiquez votre URL de webhook si vous voulez que votre serveur ait de nos nouvelles. Https uniquement, port 443, et le nom doit résoudre vers des adresses publiques.
Ce que fait votre serveur, à chaque version#
Tout est signé avec la même enveloppe que vous connaissez déjà de l'API de licence, il n'y a donc rien de nouveau à apprendre :
X-Bondry-Key: bkp_3f7a91c25e08
X-Bondry-Timestamp: 1789459200
X-Bondry-Nonce: 3f7a91c25e0844b1
X-Bondry-Signature: hex(hmac_sha256(secret, METHOD\nPATH\nTIMESTAMP\nNONCE\nsha256(corps)))
La fenêtre du timestamp est de 300 secondes, le nonce est à usage unique et le corps entre dans la signature par son sha256 : pas un octet ne change en route.
| Appel | Ce qu'il fait |
|---|---|
POST /v1/publisher/artifacts/{slug} |
Met à jour le nom, le résumé, la description par langue, le site et le canal de support. Idempotent : un champ que vous n'envoyez pas est un champ que nous ne touchons pas. L'anglais est obligatoire dans tout texte par langue |
POST /v1/publisher/artifacts/{slug}/releases |
Annonce une version : version, min_core, max_core, php, notes par langue, size, sha256 |
PUT /v1/publisher/releases/{version}/package |
Envoie le zip, brut dans le corps, jusqu'à 40 Mo. Le sha256 doit correspondre à l'annonce |
GET /v1/publisher/artifacts/{slug} |
Tout ce dont votre pipeline a besoin pour décider s'il continue : état de la revue, version publiée, empreinte de validation, dernière annonce |
GET /v1/publisher/releases/{version}/package |
Après approbation, une URL signée et courte pour récupérer le zip signé |
GET /v1/publisher/releases/{version}/hash |
L'empreinte de validation de cette version |
Annoncer coûte peu et se répète ; envoyer 40 Mo, non, et c'est pourquoi ce sont deux appels. Une annonce sans paquet expire d'elle-même au bout de sept jours, et ce numéro de version redevient libre.
Chaque refus indique le champ, ce qui est arrivé et ce qui était attendu. « Invalid payload » n'est pas un message.
La revue#
La moitié automatique tourne dès que votre paquet arrive : aucun chemin hors de
l'archive, aucun lien symbolique, aucune bombe de décompression, taille sous le
plafond, manifeste valide avec le slug enregistré et la version annoncée, version
qui avance, min_core qui existe, traduction anglaise présente, et l'analyse
statique qui lève un signalement sur eval, un grand littéral dans
base64_decode, shell_exec, une URL avec une IP fixe et du code obfusqué.
Un signalement ne refuse rien à lui seul : du code légitime les utilise tous de temps en temps. Il part chez un relecteur humain, avec le fichier et la ligne exacte.
La moitié humaine regarde votre manifeste, les permissions et les hooks que vous déclarez, les migrations et les fichiers de routes du paquet, les signalements et la différence avec votre version précédente. La décision est approuver, refuser, demander des changements ou suspendre, et toutes sauf l'approbation portent un motif écrit qui vous parvient mot pour mot.
« Demander des changements » remet la version en brouillon, le seul état où votre serveur peut renvoyer le paquet de la même version.
Le zip signé est à vous de distribuer#
Approuvée, nous signons votre zip avec la clé de paquet Bondry et enregistrons le sha256 du fichier signé. Cette empreinte est l'identité de cette version pour toujours.
Le zip signé, c'est votre zip plus deux entrées à la racine :
bondry-artifact.json {"slug":…,"version":…,"publisher":…,"files":{"<chemin>":"<sha256>"}}
bondry-artifact.sig base64 de RSA-SHA256 sur le manifeste canonique
La liste de fichiers couvre chaque entrée de votre zip d'origine, et
l'installateur vérifie dans les deux sens : rien du zip absent de la liste, rien
de la liste absent du zip. Sans cela, il suffirait d'ajouter un .php au paquet
après signature et la signature tiendrait encore.
Vous récupérez le fichier signé depuis votre compte ou par l'API, et nous supprimons notre copie dès que vous le téléchargez, et dans tous les cas sept jours après l'approbation, avec un e-mail le troisième jour si vous ne l'avez pas récupéré. Ce qui reste ici, c'est le manifeste, la signature, l'empreinte et les notes. Le fichier est à vous, sa conservation aussi.
Ce que fait l'installateur de l'acheteur#
Il calcule le sha256 du fichier qu'il a en main, vérifie la signature intégrée avec la clé publique qu'il porte déjà pour les mises à jour du core, et interroge le registre :
GET /v1/artifacts/{slug}/verify?version=1.4.2&sha256=<64 hex>
Sans identifiant, une réponse parmi trois, toujours en HTTP 200 :
| Réponse | Ce que voit l'acheteur |
|---|---|
verified |
« Fichier vérifié auprès du registre Bondry, version 1.4.2 du publicateur », et l'installation continue |
altered |
Le mur rouge : ce fichier n'est pas celui que l'auteur a publié. L'installation refuse par défaut |
unknown |
Ne figure pas au registre : traité comme n'importe quel zip trouvé sur internet |
Une version suspendue répond unknown. Sans réseau, la signature intégrée vaut
toujours et l'installateur dit qu'il n'a pas pu confirmer : une absence de réseau
n'est jamais une accusation, et jamais une approbation silencieuse.
Les événements que vous recevez#
Vous enregistrez une URL https par artefact et nous l'appelons :
| Événement | Quand |
|---|---|
artifact.approved / artifact.rejected |
la décision sur votre enregistrement |
release.approved |
la version a passé la revue et est publiée |
release.rejected |
refusée, ou renvoyée pour changements, avec le motif |
release.suspended |
retirée après publication, avec le motif |
package.altered |
une installation sous licence a reçu un fichier qui ne correspond pas à votre empreinte approuvée |
Le corps :
{
"id": "01J8ZC5E7Q2R8VQ1F0M4V8N0PA",
"type": "release.approved",
"created_at": "2026-09-17T11:31:55+00:00",
"data": { "slug": "directory", "version": "1.1.0", "validation_hash": "…" }
}
L'id est stable : le renvoi porte le même id, traitez-le donc comme votre clé
d'idempotence. Quand un type couvre plusieurs décisions, data.decision porte le
mot exact et data.reason, le motif écrit.
Vérifier la signature#
Nous signons l'appel avec le secret de webhook de cet artefact, dans la même
enveloppe que ci-dessus, avec une différence : X-Bondry-Key porte
whk_<12 hex>, l'id public du secret. C'est ainsi que votre côté sait quel
secret utiliser après une rotation, et c'est pourquoi il vaut mieux indexer vos
secrets par cet id.
$canonical = implode("\n", [
'POST',
'/votre/chemin/de/webhook',
$request->header('X-Bondry-Timestamp'),
$request->header('X-Bondry-Nonce'),
hash('sha256', $request->getContent()),
]);
$attendue = hash_hmac('sha256', $canonical, $votreSecretDe($request->header('X-Bondry-Key')));
if (! hash_equals($attendue, (string) $request->header('X-Bondry-Signature'))) {
abort(401);
}
Faire tourner le secret depuis votre compte garde le précédent valide 24 heures, pour changer votre configuration sans perdre d'événement.
Répondez 2xx en moins de 10 secondes. Sinon, nous réessayons après 1 min, 5
min, 30 min, 2 h, 12 h et 24 h, puis le point de terminaison passe en quarantaine
et vous recevez un e-mail. Chaque livraison et chaque tentative restent visibles
dans votre compte, avec la réponse de votre serveur et un bouton pour renvoyer.
Quand quelqu'un diffuse une copie altérée#
L'appel de verdict est public, et c'est pourquoi il ne déclenche jamais d'alerte : n'importe qui pourrait l'appeler mille fois avec une empreinte inventée, et le registre deviendrait un amplificateur de spam braqué sur vous.
Ce qui déclenche l'alerte, c'est l'événement : une installation sous licence
active signalant que le fichier reçu n'est pas celui que nous avons signé. Cela
devient le webhook package.altered, un e-mail pour vous avec l'empreinte reçue
et celle que nous avons approuvée, et une ligne dans notre file, groupée par
artefact et par empreinte. La même empreinte altérée apparaissant sur beaucoup de
licences, c'est du piratage à grande échelle, et c'est exactement ce que le
groupement sert à montrer.
Rien de tout cela ne révoque quoi que ce soit tout seul. Suspendre, c'est une personne, avec un motif écrit.