Ir al contenido
Bondry

Programa de publicadores

Registra tu módulo, publica una versión, recibe el paquete firmado y escucha los eventos que tu servidor necesita.

No alojamos tu módulo. Tú vendes y distribuyes donde quieras: tu propio sitio, tu propia tienda, un marketplace, un revendedor. Lo que queda aquí es la verdad sobre ese archivo, y es esa verdad la que el instalador del comprador verifica antes de extraer un solo byte.

Lo que haces, una vez#

  1. Registra el artefacto en tu cuenta, en Publicación. El slug se reserva aquí, y no en tu primera versión: es el nombre que tu módulo lleva a cada instalación, va a tu module.json y nunca cambia. Los nombres de nuestro namespace (bondry-, core, admin, designer, members, lms) se rechazan.
  2. Genera el token de publicación. Es un id bkp_ más un secreto que se muestra una vez. Un token por artefacto, revocable al instante. Revocar no tumba lo ya publicado: impide el siguiente anuncio.
  3. Indica tu URL de webhook si quieres que tu servidor sepa de nosotros. Solo https, puerto 443, y el nombre tiene que resolver a direcciones públicas.

Lo que hace tu servidor, en cada versión#

Todo se firma con el mismo sobre que ya conoces de la API de licencia, así que no hay nada nuevo que aprender:

Código
X-Bondry-Key:        bkp_3f7a91c25e08
X-Bondry-Timestamp:  1789459200
X-Bondry-Nonce:      3f7a91c25e0844b1
X-Bondry-Signature:  hex(hmac_sha256(secreto, METHOD\nPATH\nTIMESTAMP\nNONCE\nsha256(cuerpo)))

La ventana del timestamp es de 300 segundos, el nonce es de un solo uso y el cuerpo entra en la firma por su sha256, así que ni un byte cambia en el camino.

Llamada Qué hace
POST /v1/publisher/artifacts/{slug} Actualiza nombre, resumen, descripción por idioma, sitio y canal de soporte. Idempotente: el campo que no envías es el campo que no tocamos. El inglés es obligatorio en todo texto por idioma
POST /v1/publisher/artifacts/{slug}/releases Anuncia una versión: version, min_core, max_core, php, notes por idioma, size, sha256
PUT /v1/publisher/releases/{version}/package Envía el zip, crudo en el cuerpo, hasta 40 MB. El sha256 tiene que coincidir con lo anunciado
GET /v1/publisher/artifacts/{slug} Todo lo que tu pipeline necesita para decidir si sigue: estado de la revisión, versión publicada, hash de validación, último anuncio
GET /v1/publisher/releases/{version}/package Tras la aprobación, una URL firmada y corta para recoger el zip firmado
GET /v1/publisher/releases/{version}/hash El hash de validación de esa versión

Anunciar es barato y repetible; subir 40 MB, no, y por eso son dos llamadas. Un anuncio sin paquete caduca solo a los siete días, y ese número de versión vuelve a quedar libre.

Cada rechazo dice el campo, lo que llegó y lo que se esperaba. "Invalid payload" no es un mensaje.

La revisión#

La mitad automática corre en cuanto llega tu paquete: ninguna ruta fuera del archivo, ningún enlace simbólico, ninguna bomba de descompresión, tamaño dentro del tope, manifiesto válido con el slug registrado y la versión anunciada, versión que avanza, min_core que existe, traducción al inglés presente, y el escaneo estático que levanta bandera en eval, un literal grande dentro de base64_decode, shell_exec, una URL con IP fija y código ofuscado.

Una bandera no rechaza nada por sí sola: el código legítimo las usa todas de vez en cuando. Va a un revisor humano, con el archivo y la línea exacta.

La mitad humana mira tu manifiesto, los permisos y ganchos que declaras, las migraciones y los archivos de rutas del paquete, las banderas y la diferencia respecto a tu versión anterior. La decisión es aprobar, rechazar, pedir cambios o suspender, y todas menos la aprobación vienen con un motivo escrito que te llega palabra por palabra.

"Pedir cambios" devuelve la versión a borrador, que es el único estado en el que tu servidor puede enviar el paquete de la misma versión otra vez.

El zip firmado es tuyo para distribuir#

Aprobada, firmamos tu zip con la clave de paquete de Bondry y guardamos el sha256 del archivo firmado. Ese hash es la identidad de esa versión para siempre.

El zip firmado es tu zip más dos entradas en la raíz:

Código
bondry-artifact.json     {"slug":…,"version":…,"publisher":…,"files":{"<ruta>":"<sha256>"}}
bondry-artifact.sig      base64 de RSA-SHA256 sobre el manifiesto canónico

La lista de archivos cubre cada entrada de tu zip original, y el instalador verifica en los dos sentidos: nada del zip fuera de la lista, nada de la lista faltando en el zip. Sin eso, bastaría añadir un .php al paquete después de firmarlo y la firma seguiría cuadrando.

Recoges el archivo firmado desde tu cuenta o por la API, y borramos nuestra copia en cuanto lo descargas, y en todo caso siete días después de la aprobación, con un correo al tercer día si aún no lo has recogido. Lo que queda aquí es el manifiesto, la firma, el hash y las notas. El archivo es tuyo, y su custodia también.

Lo que hace el instalador del comprador#

Calcula el sha256 del archivo que tiene delante, verifica la firma incrustada con la clave pública que ya lleva para los updates del core, y le pregunta al registro:

Código
GET /v1/artifacts/{slug}/verify?version=1.4.2&sha256=<64 hex>

Sin credencial, una de tres respuestas, siempre HTTP 200:

Respuesta Lo que ve el comprador
verified "Archivo verificado con el registro de Bondry, versión 1.4.2 del publicador <tú>", y la instalación sigue
altered La pantalla roja: este archivo no es el que publicó el autor. La instalación se rechaza por defecto
unknown No consta en el registro: tratado como cualquier zip bajado de internet

Una versión suspendida responde unknown. Sin red, la firma incrustada sigue valiendo y el instalador dice que no pudo confirmar: la falta de red nunca es una acusación, y tampoco una aprobación silenciosa.

Los eventos que recibes#

Registras una URL https por artefacto y la llamamos:

Evento Cuándo
artifact.approved / artifact.rejected la decisión sobre tu registro
release.approved la versión pasó la revisión y está publicada
release.rejected rechazada, o devuelta para cambios, con el motivo
release.suspended retirada tras publicarse, con el motivo
package.altered una instalación con licencia recibió un archivo que no coincide con tu hash aprobado

El cuerpo:

JSON
{
  "id": "01J8ZC5E7Q2R8VQ1F0M4V8N0PA",
  "type": "release.approved",
  "created_at": "2026-09-17T11:31:55+00:00",
  "data": { "slug": "directory", "version": "1.1.0", "validation_hash": "…" }
}

El id es estable: el reenvío lleva el mismo id, así que trátalo como tu clave de idempotencia. Cuando un tipo cubre más de una decisión, data.decision lleva la palabra exacta y data.reason, el motivo escrito.

Cómo verificar la firma#

Firmamos la llamada con el secreto de webhook de ese artefacto, en el mismo sobre de arriba, con una diferencia: X-Bondry-Key lleva whk_<12 hex>, el id público del secreto. Así tu lado sabe qué secreto usar tras una rotación, y por eso vale la pena guardar tus secretos indexados por ese id.

PHP
$canonical = implode("\n", [
    'POST',
    '/tu/ruta/de/webhook',
    $request->header('X-Bondry-Timestamp'),
    $request->header('X-Bondry-Nonce'),
    hash('sha256', $request->getContent()),
]);

$esperada = hash_hmac('sha256', $canonical, $tuSecretoDe($request->header('X-Bondry-Key')));

if (! hash_equals($esperada, (string) $request->header('X-Bondry-Signature'))) {
    abort(401);
}

Rotar el secreto desde tu cuenta mantiene el anterior válido 24 horas, para que cambies tu configuración sin perder ningún evento.

Responde 2xx en 10 segundos. Fuera de eso, reintentamos en 1 min, 5 min, 30 min, 2 h, 12 h y 24 h, y luego el endpoint entra en cuarentena y recibes un correo. Cada entrega y cada intento quedan visibles en tu cuenta, con la respuesta que dio tu servidor y un botón para enviarlo de nuevo.

Cuando alguien distribuye una copia alterada#

La llamada de veredicto es pública, y por eso nunca genera alerta: cualquiera podría llamarla mil veces con un hash inventado, y el registro se volvería un amplificador de spam apuntado hacia ti.

Quien genera alerta es el evento: una instalación con licencia activa diciendo que el archivo que recibió no es el que firmamos. Eso se convierte en el webhook package.altered, un correo para ti con el hash recibido y el hash que aprobamos, y una línea en nuestra cola, agrupada por artefacto y por hash. El mismo hash alterado apareciendo en muchas licencias es piratería a escala, y eso es justo lo que el agrupamiento existe para mostrar.

Nada de esto revoca nada por sí solo. Quien suspende es una persona, con un motivo escrito.

Privacidad

Este sitio usa solo cookies necesarias: sesión, idioma, tema y la verificación antispam de hCaptcha en los formularios. No hay seguimiento ni publicidad. Política de privacidad