Programa de publicadores
Registre o seu módulo, publique uma versão, receba o pacote assinado e ouça os eventos que o seu servidor precisa.
Nós não hospedamos o seu módulo. Você vende e distribui onde quiser: site próprio, loja própria, marketplace, revendedor. O que fica aqui é a verdade sobre aquele arquivo, e é essa verdade que o instalador do comprador confere antes de extrair um único byte.
O que você faz, uma vez#
- Cadastre o artefato na sua conta, em Publicação. O slug é reservado
aqui, e não na primeira versão: ele é o nome que o seu módulo leva para toda
instalação, vai para o seu
module.jsone nunca muda. Nomes do nosso namespace (bondry-,core,admin,designer,members,lms) são recusados. - Gere o token de publicação. Ele é um id
bkp_mais um segredo mostrado uma vez. Um token por artefato, revogável na hora. Revogar não derruba o que já foi publicado: impede o próximo anúncio. - Informe a URL de webhook se quiser que o seu servidor ouça de nós. Só https, porta 443, e o nome precisa resolver para endereços públicos.
O que o seu servidor faz, a cada versão#
Tudo é assinado com o mesmo envelope que você já conhece da API de licença, então não há nada novo para aprender:
X-Bondry-Key: bkp_3f7a91c25e08
X-Bondry-Timestamp: 1789459200
X-Bondry-Nonce: 3f7a91c25e0844b1
X-Bondry-Signature: hex(hmac_sha256(segredo, METHOD\nPATH\nTIMESTAMP\nNONCE\nsha256(corpo)))
A janela do timestamp é de 300 segundos, o nonce é de uso único e o corpo entra na assinatura pelo sha256, então nem um byte muda no caminho.
| Chamada | O que faz |
|---|---|
POST /v1/publisher/artifacts/{slug} |
Atualiza nome, resumo, descrição por idioma, site e canal de suporte. Idempotente: campo que você não manda é campo que não mexemos. O inglês é obrigatório em todo texto por idioma |
POST /v1/publisher/artifacts/{slug}/releases |
Anuncia uma versão: version, min_core, max_core, php, notes por idioma, size, sha256 |
PUT /v1/publisher/releases/{version}/package |
Envia o zip, cru no corpo, até 40 MB. O sha256 precisa bater com o anunciado |
GET /v1/publisher/artifacts/{slug} |
Tudo o que o seu pipeline precisa para decidir se segue: estado da revisão, versão publicada, hash de validação, último anúncio |
GET /v1/publisher/releases/{version}/package |
Depois da aprovação, uma URL assinada e curta para buscar o zip assinado |
GET /v1/publisher/releases/{version}/hash |
O hash de validação daquela versão |
Anunciar é barato e pode ser repetido; subir 40 MB, não, e é por isso que são duas chamadas. Um anúncio sem pacote expira sozinho em sete dias, e aquele número de versão volta a ficar livre.
Toda recusa diz o campo, o que chegou e o que se esperava. "Invalid payload" não é mensagem.
A revisão#
A metade automática roda assim que o seu pacote chega: nenhum caminho para fora
do arquivo, nenhum link simbólico, nenhuma bomba de descompressão, tamanho no
teto, manifesto válido com o slug cadastrado e a versão anunciada, versão que
avança, min_core que existe, tradução do inglês presente, e a varredura
estática que acende bandeira em eval, literal grande dentro de
base64_decode, shell_exec, URL com IP fixo e código ofuscado.
Bandeira não reprova nada sozinha: código legítimo usa todas elas de vez em quando. Ela vai para um revisor humano, com o arquivo e a linha exata.
A metade humana olha o seu manifesto, as permissões e os ganchos que você declara, as migrações e os arquivos de rota do pacote, as bandeiras e a diferença em relação à sua versão anterior. A decisão é aprovar, reprovar, pedir mudança ou suspender, e todas elas, menos a aprovação, vêm com um motivo escrito que chega até você palavra por palavra.
"Pedir mudança" devolve a versão para rascunho, que é o único estado em que o seu servidor pode enviar o pacote da mesma versão de novo.
O zip assinado é seu para distribuir#
Aprovada, assinamos o seu zip com a chave de pacote do Bondry e gravamos o sha256 do arquivo assinado. Esse hash é a identidade daquela versão para sempre.
O zip assinado é o seu zip mais duas entradas na raiz:
bondry-artifact.json {"slug":…,"version":…,"publisher":…,"files":{"<caminho>":"<sha256>"}}
bondry-artifact.sig base64 de RSA-SHA256 sobre o manifesto canônico
A lista de arquivos cobre toda entrada do seu zip original, e o instalador
confere os dois sentidos: nada do zip fora da lista, nada da lista faltando no
zip. Sem isso, bastaria acrescentar um .php ao pacote depois de assinado e a
assinatura continuaria conferindo.
Você busca o arquivo assinado na sua conta ou pela API, e apagamos a nossa cópia assim que você baixa, e em todo caso sete dias depois da aprovação, com um e-mail no terceiro dia se você ainda não tiver buscado. O que fica aqui é o manifesto, a assinatura, o hash e as notas. O arquivo é seu, e a guarda também.
O que o instalador do comprador faz#
Ele calcula o sha256 do arquivo que está na mão dele, confere a assinatura embutida com a chave pública que já carrega para os updates do core, e pergunta ao registro:
GET /v1/artifacts/{slug}/verify?version=1.4.2&sha256=<64 hex>
Sem credencial, uma de três respostas, sempre HTTP 200:
| Resposta | O que o comprador vê |
|---|---|
verified |
"Arquivo conferido com o registro do Bondry, versão 1.4.2 do publicador <você>", e a instalação segue |
altered |
A tela vermelha: este arquivo não é o que o autor publicou. A instalação recusa por padrão |
unknown |
Não consta no registro: tratado como um zip qualquer baixado da internet |
Uma versão suspensa responde unknown. Sem rede, a assinatura embutida ainda
vale e o instalador diz que não conseguiu confirmar: falta de rede nunca é
acusação, e também não é aprovação silenciosa.
Os eventos que você recebe#
Você cadastra uma URL https por artefato e nós a chamamos:
| Evento | Quando |
|---|---|
artifact.approved / artifact.rejected |
a decisão sobre o seu cadastro |
release.approved |
a versão passou na revisão e está publicada |
release.rejected |
reprovada, ou devolvida para mudança, com o motivo |
release.suspended |
tirada do ar depois de publicada, com o motivo |
package.altered |
uma instalação licenciada recebeu um arquivo que não bate com o seu hash aprovado |
O corpo:
{
"id": "01J8ZC5E7Q2R8VQ1F0M4V8N0PA",
"type": "release.approved",
"created_at": "2026-09-17T11:31:55+00:00",
"data": { "slug": "directory", "version": "1.1.0", "validation_hash": "…" }
}
O id é estável: o reenvio leva o mesmo id, então trate-o como a sua chave de
idempotência. Quando um tipo cobre mais de uma decisão, data.decision carrega
a palavra exata e data.reason, o motivo escrito.
Como conferir a assinatura#
Assinamos a chamada com o segredo de webhook daquele artefato, no mesmo envelope
de cima, com uma diferença: o X-Bondry-Key carrega whk_<12 hex>, o id
público do segredo. É assim que o seu lado sabe qual segredo usar depois de uma
rotação, e é por isso que vale guardar os seus segredos indexados por esse id.
$canonical = implode("\n", [
'POST',
'/seu/caminho/de/webhook',
$request->header('X-Bondry-Timestamp'),
$request->header('X-Bondry-Nonce'),
hash('sha256', $request->getContent()),
]);
$esperada = hash_hmac('sha256', $canonical, $seuSegredoDe($request->header('X-Bondry-Key')));
if (! hash_equals($esperada, (string) $request->header('X-Bondry-Signature'))) {
abort(401);
}
Rotacionar o segredo pela sua conta mantém o anterior válido por 24 horas, para você trocar a configuração sem perder evento nenhum.
Responda 2xx em até 10 segundos. Fora disso, tentamos de novo em 1 min, 5 min,
30 min, 2 h, 12 h e 24 h, e depois o endpoint entra em quarentena e você recebe
um e-mail. Toda entrega e toda tentativa ficam visíveis na sua conta, com a
resposta que o seu servidor deu e um botão para enviar de novo.
Quando alguém distribui uma cópia adulterada#
A chamada de veredito é pública, e por isso ela nunca gera alerta: qualquer pessoa poderia chamá-la mil vezes com um hash inventado, e o registro viraria um amplificador de spam apontado para você.
Quem gera alerta é o evento: uma instalação com licença ativa dizendo que o
arquivo que ela recebeu não é o que assinamos. Isso vira o webhook
package.altered, um e-mail para você com o hash recebido e o hash que
aprovamos, e uma linha na nossa fila, agrupada por artefato e por hash. O mesmo
hash adulterado aparecendo em muitas licenças é pirataria em escala, e é
exatamente isso que o agrupamento existe para mostrar.
Nada disso revoga coisa alguma sozinho. Quem suspende é gente, com motivo escrito.