Webhooks
Eventos de licença entregues ao seu servidor, assinados e com retentativa.
Eventos
| Evento | Quando dispara |
|---|---|
license.created | Licença emitida |
license.activated | Primeira ativação |
license.deactivated | Instalação desvinculada |
license.revoked | Licença revogada |
license.expired | Licença expirada |
Cadastre endpoints em /seller/licensing/webhooks. Disponível a partir do plano Starter.
Corpo
{
"id": "evt_a1b2c3d4e5f6a7b8c9d0",
"type": "license.activated",
"createdAt": "2026-11-11T22:00:00.000Z",
"data": {
"license": {
"id": "clx1a2b3c4d5e6f7g8h9",
"productId": "lsp_a1b2c3d4e5f6a7b8c9d0e1f2",
"pluginName": "Aurum Economy",
"status": "ACTIVE",
"source": "PURCHASE",
"isTest": false,
"issuedAt": "2026-11-10T14:22:31.000Z",
"expiresAt": null,
"maxInstallations": 2,
"activeInstallations": 1,
"keyHint": "K7P2"
}
}
}O que NÃO vai no corpo
keyHint (últimos 4 caracteres) existe para você casar com o seu próprio registro, se precisar.Cabeçalhos
| Cabeçalho | Para quê |
|---|---|
X-Lodestone-Event | Tipo do evento. |
X-Lodestone-Delivery | Identificador da entrega. ESTÁVEL entre tentativas — use para deduplicar. |
X-Lodestone-Signature | t=<epoch>,v1=<hex> — HMAC-SHA256. |
User-Agent | Lodestone-Licensing-Webhooks/1.0 |
Verificar a assinatura
Verifique antes de confiar no corpo
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verificar(req, segredo) {
const cabecalho = req.headers['x-lodestone-signature'] // t=...,v1=...
if (!cabecalho) return false
const campos = Object.fromEntries(
cabecalho.split(',').map((parte) => parte.split('=')),
)
// Rejeita entrega antiga reapresentada. O timestamp está DENTRO da
// assinatura, então não dá para alterá-lo sem invalidar o v1.
const agora = Math.floor(Date.now() / 1000)
if (Math.abs(agora - Number(campos.t)) > 300) return false
const esperado = createHmac('sha256', segredo)
.update(`${campos.t}.${req.rawBody}`) // corpo CRU
.digest('hex')
const a = Buffer.from(esperado)
const b = Buffer.from(campos.v1 ?? '')
return a.length === b.length && timingSafeEqual(a, b)
}Use o corpo cru
express.raw({ type: "application/json" }).Java
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(segredo.getBytes(UTF_8), "HmacSHA256"));
byte[] calculado = mac.doFinal((timestamp + "." + corpoCru).getBytes(UTF_8));
// Comparação em tempo constante.
boolean confere = MessageDigest.isEqual(calculado, HexFormat.of().parseHex(v1));Retentativa
Entregas que falham são repetidas até 6 vezes, com espera crescente: 30s, 2 min, 10 min, 1 h, 6 h.
- Responda 2xx para confirmar. Qualquer outro status conta como falha.
- Responda rápido. O timeout é de 8 segundos. Enfileire o trabalho pesado e responda imediatamente.
- Deduplique por
X-Lodestone-Delivery. O identificador é o mesmo em todas as tentativas da mesma entrega.
Endpoints mortos são desativados sozinhos
Restrições de destino
Um webhook é SSRF em potencial. Por isso:
- Apenas HTTPS, apenas porta 443.
- O host precisa resolver para endereço público. Rede privada, loopback, link-local, multicast e os ranges de metadados de nuvem são recusados.
- A validação roda no cadastro e de novo a cada entrega, contra DNS rebinding.
- Redirecionamento não é seguido.
Segredo
Cada endpoint tem segredo próprio, mostrado uma única vez na criação. Ele fica cifrado no banco com AES-256-GCM — precisa ser recuperável para assinar cada entrega, então não pode ser hash.
A rotação gera um segredo novo imediatamente. Atualize o seu servidor antes de considerar a troca concluída: as entregas seguintes já usam o novo.
Licenças de teste não disparam
Licenças do Playground não geram webhook. Para testar a sua integração, emita uma licença normal para um e-mail seu e revogue-a depois.