API & MCP

Webhooks de saída

Em vez de o seu sistema perguntar de tempos em tempos se apareceu algo novo, a ionluz avisa. Cadastre uma URL em API e cobertura, guarde o segredo que aparece na hora e passe a receber os eventos da sua conta.

EventoQuando dispara
lead.criadoUma proposta foi gravada na sua conta: pela página pública de captação, por POST /v1/quote com gravação ou pelo MCP com persist.
proposta.abertaO cliente final abriu a proposta pela primeira vez. Sai uma vez por proposta, mesmo gatilho do e-mail de aviso.
proposta.aceitaO cliente final aceitou a proposta na página dele, dentro da validade. Sai uma vez por proposta, mesmo gatilho do e-mail de aceite.
pingVocê clicou em Testar no painel. Use para validar a sua verificação de assinatura antes de contar com os eventos reais.

O que chega

POST https://seucrm.com.br/hooks/ionluz
X-Ionluz-Event: lead.criado
X-Ionluz-Delivery: evt_3f1a9c72b0d84e5f16
X-Ionluz-Signature: t=1789012345,v1=6b1f...c904
Content-Type: application/json

{
  "id": "evt_3f1a9c72b0d84e5f16",
  "evento": "lead.criado",
  "criadoEm": "2026-09-12T14:05:00.000Z",
  "data": {
    "proposalId": "clx8h2k9a0001abcd",
    "origem": "api",
    "criadaEm": "2026-09-12T14:05:00.000Z",
    "potenciaKwp": 5.4,
    "valorFinal": 28500,
    "paybackAnos": 6.3,
    "consumoKwhMes": 600,
    "cliente": { "nome": "Ana", "email": "ana@ex.com", "telefone": "15999990000",
                 "cidade": "Sorocaba", "uf": "SP" },
    "links": { "proposta": "https://app.ionluz.com.br/p/...", "pdf": "https://app.ionluz.com.br/api/pub/.../pdf?t=..." }
  }
}

Conferindo a assinatura

O header X-Ionluz-Signature vem no formato t=<unix>,v1=<hmac-sha256-hex>. O HMAC é calculado com o seu segredo sobre a string <t>.<corpo bruto>. Compare em tempo constante e recuse timestamp com mais de 5 minutos, isso fecha a porta para reenvio de uma entrega capturada.

// Express: guarde o corpo BRUTO, a assinatura é sobre os bytes recebidos
app.post("/hooks/ionluz", express.raw({ type: "application/json" }), (req, res) => {
  const bruto = req.body.toString("utf8");
  const partes = Object.fromEntries(
    req.get("X-Ionluz-Signature").split(",").map(p => p.split("="))
  );
  const esperado = crypto.createHmac("sha256", process.env.IONLUZ_WEBHOOK_SECRET)
    .update(partes.t + "." + bruto).digest("hex");

  const assinaturaOk =
    esperado.length === partes.v1.length &&
    crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(partes.v1));
  const recente = Math.abs(Date.now() / 1000 - Number(partes.t)) < 300;
  if (!assinaturaOk || !recente) return res.sendStatus(401);

  res.sendStatus(200);            // responda primeiro
  processar(JSON.parse(bruto));   // trabalhe depois, fora do ciclo da entrega
});

Entrega

  • Timeout de 8 segundos e uma retentativa, em falha de rede, 5xx ou 429. Qualquer outro 4xx encerra a entrega, porque repetir não mudaria o resultado.
  • A retentativa reenvia o mesmo corpo e a mesma assinatura. Deduplique pelo X-Ionluz-Delivery, que é o id do envelope.
  • Responda 2xx primeiro e processe depois. Enquanto o seu servidor pensa, o relógio dos 8 segundos corre.
  • Sem fila de reprocessamento: o que acontecer com o webhook removido ou fora do ar por muito tempo não é reenviado depois. Para reconciliar, use GET /v1/proposals.
  • https e host público. Redirect não é seguido.
  • O painel mostra o status da última entrega. Rotacionar o segredo vale na entrega seguinte.
  • Emitir um evento nunca atrasa nem derruba a resposta ao seu cliente: a cotação volta antes, o aviso sai depois.
API v1 · ionluz, orçamentos solares. Dúvidas? Cite o requestId ao escrever para suporte@ionluz.com.br.
Webhooks de saída · API ionluz