Skip to content

Receber o webhook ​

Tipo
Como fazer
Para
Desenvolvedor
Na tela
HEC › Tenants › Avançado › Canais de Alerta

O receptor do webhook é qualquer endpoint HTTPS público que aceite um POST JSON e responda 2xx em até 10 segundos. Dá para ver o POST chegando num serviço de inspeção antes de ter o seu; o receptor de verdade precisa ainda autenticar a origem e descartar repetições.

Testar sem servidor ​

  1. Abra um serviço de inspeção de requisições, como o webhook.site, e copie a URL única que ele gera.
  2. Cole a URL em URL do Webhook no Tenant (veja configurar o webhook).
  3. Num alerta com o canal Webhook marcado, use Forçar Envio na lista de alertas.
  4. Confira no serviço o corpo, os cabeçalhos e o número de POSTs (um por destinatário).

A URL do serviço de inspeção fica visível para quem a tiver. Troque pela URL definitiva depois do teste.

Para testar o seu receptor antes de ligar no Lumo, mande um corpo de exemplo com curl:

bash
curl -X POST https://seu-endpoint.exemplo.com/lumo/alertas \
  -H 'Content-Type: application/json' \
  -H 'X-Auth-Token: seu-token-secreto' \
  -d '{"alert":{"id":412,"nome":"Teste","type":"trigger","app_id":null},
       "user":{"id":87,"nome":"Maria","email":"maria@empresa.com.br"},
       "tenantId":23,"timestamp":"2026-10-30T10:30:00.512Z",
       "content":[{"id":"x","type":"text","nome":"Resumo","generated":{"text":"Olá"}}]}'

O corpo completo está em payload.

Autenticar a origem ​

O Lumo não assina o POST. Para o receptor recusar quem não é o Lumo, use um dos dois, ou os dois juntos:

  • Cabeçalho secreto: cadastre em Headers HTTP Customizados um cabeçalho como X-Auth-Token com um valor longo e aleatório, e recuse o POST sem ele.
  • Caminho secreto: ponha um trecho aleatório no caminho da URL (/lumo/alertas/7f3a9b2e4c1d). Quem não conhece o caminho recebe 404.

Gere o segredo com openssl rand -hex 24 e troque se ele vazar.

Descartar repetições ​

O Lumo repete o POST até 8 vezes quando não recebe 2xx a tempo. Se o receptor processou e a resposta se perdeu, o mesmo POST chega de novo. Não existe chave de idempotência no cabeçalho; monte a sua com três campos do corpo:

<alert.id>:<user.id>:<timestamp>

O timestamp é fixado quando o POST é criado e não muda entre tentativas. Cada destinatário tem o próprio POST, com timestamp próprio; o user.id entra na chave para que dois POSTs de pessoas diferentes nunca se confundam, mesmo criados no mesmo milissegundo.

Guarde as chaves já vistas por pelo menos 10 minutos, tempo de sobra para as 8 tentativas.

Responder rápido ​

O tempo limite é de 10 segundos por tentativa. Responda 200 logo depois de validar e enfileirar, e faça o trabalho pesado (baixar arquivos, chamar outros sistemas) depois. Responder 4xx também gera nova tentativa; para recusar um POST de vez, responda 2xx e descarte.

n8n ​

  1. Crie um nó Webhook com método POST e um path secreto.
  2. Em Response Mode, use Immediately, para o n8n responder 200 antes de processar.
  3. Copie a URL de produção do nó para URL do Webhook no Tenant.
  4. Roteie com um nó Switch sobre {{$json.alert.id}} ou {{$json.alert.nome}}, já que a URL é a mesma para todos os alertas do Tenant.

Node.js ​

js
import express from "express";

const app = express();
app.use(express.json({ limit: "5mb" }));

const TOKEN = process.env.LUMO_WEBHOOK_TOKEN;
const vistos = new Map(); // chave -> expira em (ms)

app.post("/lumo/alertas", (req, res) => {
  if (req.get("X-Auth-Token") !== TOKEN) return res.sendStatus(401);

  const { alert, user, timestamp, content } = req.body;
  const chave = `${alert.id}:${user.id}:${timestamp}`;
  const agora = Date.now();
  for (const [k, exp] of vistos) if (exp < agora) vistos.delete(k);
  if (vistos.has(chave)) return res.sendStatus(200);
  vistos.set(chave, agora + 10 * 60 * 1000);

  res.sendStatus(200);
  setImmediate(() => processar(alert, user, content));
});

function processar(alert, user, content) {
  for (const bloco of content) {
    const g = bloco.generated;
    if (bloco.type === "text" || bloco.type === "ai") console.log(bloco.nome, g.text);
    if (bloco.type === "report") console.log(bloco.nome, g.pdf, g.xlsx);
    if (bloco.type === "chart" && g.kind === "dashboard") console.log("dashboard", g.url);
    else if (bloco.type === "chart" && (g.chart || g.error)) console.log("gráfico", g.chart ?? g.error);
    else if (bloco.type === "chart") console.log("bloco sem conteúdo (falhou)");
  }
}

app.listen(3000);

Com mais de uma instância do receptor, troque o Map por um armazenamento compartilhado, como Redis com SET chave 1 NX EX 600.

Python ​

python
import os, time
from fastapi import FastAPI, Request, BackgroundTasks, HTTPException

app = FastAPI()
TOKEN = os.environ["LUMO_WEBHOOK_TOKEN"]
vistos: dict[str, float] = {}

@app.post("/lumo/alertas")
async def receber(req: Request, tarefas: BackgroundTasks):
    if req.headers.get("x-auth-token") != TOKEN:
        raise HTTPException(status_code=401)
    corpo = await req.json()
    chave = f'{corpo["alert"]["id"]}:{corpo["user"]["id"]}:{corpo["timestamp"]}'
    agora = time.time()
    for k in [k for k, exp in vistos.items() if exp < agora]:
        del vistos[k]
    if chave in vistos:
        return {"ok": True}
    vistos[chave] = agora + 600
    tarefas.add_task(processar, corpo)
    return {"ok": True}

def processar(corpo: dict):
    for bloco in corpo["content"]:
        print(bloco["type"], bloco["nome"], bloco["generated"])

Arquivos ​

PDFs, planilhas e imagens chegam como links. Baixe o arquivo durante o processamento e guarde no seu armazenamento, em vez de gravar o link do Lumo como endereço permanente.

Quando a entrega falha ​

SintomaCausa provável
Nenhum POST chegaCanal Webhook desmarcado no alerta, ou o canal não está liberado em Canais de Alerta
Falhou com Invalid webhook URLURL vazia no Tenant e no Cliente
O Tenant não salva a URLA URL quebra uma das regras: https, porta 443, sem usuário e senha, endereço público
Falhou depois de 4 a 6 minutosO receptor demora mais de 10 segundos, responde fora de 2xx ou tem certificado TLS inválido
O mesmo conteúdo chega duas vezesA resposta 2xx chegou tarde. Descarte pela chave de repetição
Um bloco chega com error ou com generated vazioA geração daquele bloco falhou (Gráfico traz error; Dashboard e Relatório chegam {}). Os outros vêm normais
Cabeçalho customizado não chegaNome reservado ou inválido; o editor de cabeçalhos aponta o erro

No Histórico de Envio do alerta, cada destinatário mostra o canal Webhook como Na fila, Enviado ou Falhou. Na falha, aparece o erro da última tentativa, como HTTP 500: Internal Server Error ou Request timeout after 10000ms; a resposta do receptor não aparece na tela. Esgotadas as tentativas, o criador do alerta e os administradores do Tenant recebem uma notificação no Lumo.

O Lumo não publica lista de IPs de origem; a autenticação tem que vir do cabeçalho ou do caminho secreto.

Relacionado ​