API & MCP

Webhooks de saída

Em vez de o seu sistema perguntar de tempos em tempos se apareceu algo novo, o ionluz avisa. Cadastre uma URL em API e cobertura, em qualquer plano, 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

O lead.criado da gravação do exemplo de Cotação:

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-25T14:05:00.000Z",
  "data": {
    "proposalId": "clx8h2k9a0001abcd",
    "origem": "api",
    "criadaEm": "2026-09-25T14:05:00.000Z",
    "potenciaKwp": 5.72,
    "valorFinal": 14296.8,
    "paybackAnos": 2.1406530441031313,
    "consumoKwhMes": 620,
    "cliente": {
      "nome": "Cliente Exemplo",
      "email": "cliente@exemplo.com.br",
      "telefone": "(15) 99999-0000",
      "cidade": "Sorocaba",
      "uf": "SP"
    },
    "links": {
      "proposta": "https://app.ionluz.com.br/p/clx8h2k9a0001abcd?t=...",
      "pdf": "https://app.ionluz.com.br/api/pub/clx8h2k9a0001abcd/pdf?t=..."
    }
  }
}

Conferindo a assinatura

O cabeçalho 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, confira o tamanho antes da comparação (com tamanhos diferentes, timingSafeEqual lança erro) e recuse timestamp com mais de 5 minutos, o que fecha a porta para o 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(
    String(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 =
    typeof partes.v1 === "string" && 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.
  • Só 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.
  • Nas contas com o motor novo, lead.criado sai quando o pedido é gravado, com o valorFinal da sua tabela, mesmo que o cliente ainda não veja preço ("eu aprovo antes" pendente ou publicação só com o pedido). Esse número é para o seu sistema: antes de mandá-lo ao cliente, confira aprovacao e publicacao em GET /v1/proposals/{id}. A aprovação não dispara evento.
API v1 · ionluz, propostas solares honestas. Dúvidas? Cite o requestId ao escrever para suporte@ionluz.com.br.
Webhooks de saída · API ionluz