Webhooks
Papel do recurso
Section titled “Papel do recurso”Webhooks avisam um sistema externo sempre que um agendamento é criado ou alterado no xLumen. A cada evento, o xLumen envia uma requisição HTTP para a URL cadastrada, com os dados do agendamento no corpo, e o sistema receptor não precisa consultar o xLumen periodicamente para saber o que mudou.
Os webhooks são complementares à API: o envio traz os dados principais do agendamento e, quando o receptor precisar de algo que não vem no corpo, consulta a API.
Eventos e o que os dispara
Section titled “Eventos e o que os dispara”Os webhooks são disparados a partir de eventos do módulo de Agenda. No cadastro do webhook, o administrador escolhe quais eventos aquele webhook recebe; um mesmo webhook pode estar inscrito em um ou nos dois.
| Evento | Quando é enviado |
|---|---|
AGENDAMENTO_CRIADO | um agendamento é criado, seja pelo painel, pela API, pelo agente de IA ou como o novo agendamento gerado por um reagendamento |
AGENDAMENTO_ATUALIZADO | um agendamento é cancelado, concluído ou reagendado, ou tem título, descrição, valor ou a notificação por e-mail alterados |
Para saber o que mudou em um AGENDAMENTO_ATUALIZADO, compare statusAnterior com status no corpo:
ATIVO→CANCELADO: cancelamentoATIVO→CONCLUIDO: conclusãoATIVO→REAGENDADO: reagendamentoATIVO→ATIVO: edição de dados, sem mudança de status
O reagendamento gera dois envios:
AGENDAMENTO_ATUALIZADOdo agendamento original, comstatusREAGENDADOereagendadoParaapontando para o novo agendamentoAGENDAMENTO_CRIADOdo novo agendamento, comreagendamentoDeapontando para o original
Alterar os valores de campos personalizados de agendamento não dispara webhook.
Cadastro e secret
Section titled “Cadastro e secret”Cada webhook é cadastrado com:
- nome ou descrição para identificação na lista e no log
- URL de destino, que recebe as requisições
- eventos que disparam o webhook
- secret opcional, usado para assinar as requisições
O secret pode ser digitado ou criado pelo botão Gerar no cadastro, e só pode ser visto e copiado nesse momento: guarde o valor no sistema receptor antes de salvar. Depois de salvo, o secret não é exibido de novo. Se ele se perder, troque por um novo no cadastro do webhook e atualize o receptor.
Requisição
Section titled “Requisição”Cada envio é uma requisição POST com corpo JSON para a URL cadastrada, com estes headers:
| Header | Conteúdo |
|---|---|
content-type | application/json |
x-xlumen-event | nome do evento: AGENDAMENTO_CRIADO ou AGENDAMENTO_ATUALIZADO |
x-xlumen-tenant-id | identificador numérico da conta no xLumen |
x-xlumen-signature | assinatura HMAC-SHA256 do corpo, em hexadecimal; enviado só quando o webhook tem secret |
Os dois eventos usam o mesmo formato. Toda chave é sempre enviada; quando não há valor, ela chega como null. Datas vêm em ISO 8601, em UTC.
| Campo | O que é |
|---|---|
version | versão do formato do corpo; hoje, "v1" |
evento | nome do evento, igual ao header x-xlumen-event |
dominio | domínio da conta no xLumen |
occurredAt | momento em que o envio foi gerado |
data.agendamento.id | ID do agendamento, o mesmo usado na API |
data.agendamento.titulo | título do agendamento |
data.agendamento.dataInicial | início do agendamento |
data.agendamento.dataFinal | fim do agendamento |
data.agendamento.tempoEmMinutos | duração em minutos |
data.agendamento.valor | valor do agendamento como texto decimal (ex.: "150.00"), ou null |
data.agendamento.status | status atual: ATIVO, CANCELADO, REAGENDADO ou CONCLUIDO |
data.agendamento.statusAnterior | status antes da alteração, com os mesmos valores de status; null em AGENDAMENTO_CRIADO |
data.agendamento.descricao | descrição do agendamento |
data.agendamento.observacoes | observações do agendamento |
data.agendamento.motivoCancelamento | motivo informado no cancelamento |
data.agendamento.agendadoPeloAgente | true quando o agendamento foi criado pelo agente de IA |
data.agendamento.canceladoPeloAgente | true quando o agendamento foi cancelado pelo agente de IA |
data.agendamento.agendadoPeloUsuario | usuário que criou o agendamento, como { id, nome } |
data.agendamento.canceladoPeloUsuario | usuário que cancelou o agendamento, como { id, nome } |
data.agendamento.prestador | prestador do agendamento, como { id, nome } |
data.agendamento.cliente | cliente do agendamento, como { id, nome } |
data.agendamento.servico | serviço do agendamento, como { id, nome } |
data.agendamento.reagendamentoDe | agendamento original, quando este foi gerado por um reagendamento, como { id, dataInicial, dataFinal, status } |
data.agendamento.reagendadoPara | novo agendamento, quando este foi reagendado, como { id, dataInicial, dataFinal, status } |
data.agendamento.createdAt | data de criação do registro |
data.agendamento.updatedAt | data da última alteração do registro |
O corpo não traz o idExterno do agendamento, os valores de campos personalizados nem o telefone ou o e-mail do cliente. Para esses dados, consulte a API com o id recebido.
Exemplo: AGENDAMENTO_CRIADO
Section titled “Exemplo: AGENDAMENTO_CRIADO”Valores ilustrativos:
{ "version": "v1", "evento": "AGENDAMENTO_CRIADO", "dominio": "minhaclinica", "occurredAt": "2026-10-13T14:30:02.118Z", "data": { "agendamento": { "id": 4821, "titulo": "Consulta de avaliação", "dataInicial": "2026-10-20T13:00:00.000Z", "dataFinal": "2026-10-20T13:50:00.000Z", "tempoEmMinutos": 50, "valor": "150.00", "status": "ATIVO", "statusAnterior": null, "descricao": null, "observacoes": "Primeira consulta", "motivoCancelamento": null, "agendadoPeloAgente": false, "canceladoPeloAgente": false, "agendadoPeloUsuario": { "id": 12, "nome": "Ana Souza" }, "canceladoPeloUsuario": null, "prestador": { "id": 3, "nome": "Dra. Beatriz Lima" }, "cliente": { "id": 905, "nome": "Carlos Pereira" }, "servico": { "id": 7, "nome": "Avaliação" }, "reagendamentoDe": null, "reagendadoPara": null, "createdAt": "2026-10-13T14:30:01.954Z", "updatedAt": "2026-10-13T14:30:01.954Z" } }}Exemplo: AGENDAMENTO_ATUALIZADO (cancelamento)
Section titled “Exemplo: AGENDAMENTO_ATUALIZADO (cancelamento)”Valores ilustrativos:
{ "version": "v1", "evento": "AGENDAMENTO_ATUALIZADO", "dominio": "minhaclinica", "occurredAt": "2026-10-15T18:12:45.507Z", "data": { "agendamento": { "id": 4821, "titulo": "Consulta de avaliação", "dataInicial": "2026-10-20T13:00:00.000Z", "dataFinal": "2026-10-20T13:50:00.000Z", "tempoEmMinutos": 50, "valor": "150.00", "status": "CANCELADO", "statusAnterior": "ATIVO", "descricao": null, "observacoes": "Primeira consulta", "motivoCancelamento": "Cliente pediu para remarcar no próximo mês", "agendadoPeloAgente": false, "canceladoPeloAgente": true, "agendadoPeloUsuario": { "id": 12, "nome": "Ana Souza" }, "canceladoPeloUsuario": null, "prestador": { "id": 3, "nome": "Dra. Beatriz Lima" }, "cliente": { "id": 905, "nome": "Carlos Pereira" }, "servico": { "id": 7, "nome": "Avaliação" }, "reagendamentoDe": null, "reagendadoPara": null, "createdAt": "2026-10-13T14:30:01.954Z", "updatedAt": "2026-10-15T18:12:45.391Z" } }}Validar a assinatura
Section titled “Validar a assinatura”Quando o webhook tem secret, cada requisição traz o header x-xlumen-signature. Para confirmar que ela veio do xLumen e que o corpo não foi alterado:
- Leia o corpo bruto da requisição, exatamente como chegou, antes de qualquer conversão para objeto. Não reserialize o JSON: qualquer diferença de espaço ou ordem de chaves muda a assinatura.
- Calcule o HMAC-SHA256 desse corpo usando o secret como chave.
- Converta o resultado para hexadecimal minúsculo (64 caracteres).
- Compare com o valor do header
x-xlumen-signatureusando comparação em tempo constante. - Se não conferir, ou se o header não vier, rejeite a requisição.
Exemplo em Node.js:
const crypto = require('node:crypto');
function assinaturaValida(corpoBruto, assinaturaRecebida, secret) { const esperada = crypto.createHmac('sha256', secret).update(corpoBruto).digest('hex'); const a = Buffer.from(esperada, 'utf8'); const b = Buffer.from(String(assinaturaRecebida || ''), 'utf8'); return a.length === b.length && crypto.timingSafeEqual(a, b);}corpoBruto é o corpo recebido como Buffer ou texto, sem passar por JSON.parse. No Express, por exemplo, use express.raw({ type: 'application/json' }) na rota do webhook e faça o JSON.parse só depois de validar.
Entrega e desativação
Section titled “Entrega e desativação”- O envio é considerado bem-sucedido quando o receptor responde com status HTTP
2xxem até 10 segundos. Qualquer outra resposta, erro de conexão ou tempo esgotado conta como falha. - Redirecionamentos (
3xx) não são seguidos e contam como falha. Cadastre a URL final do receptor. - Não há reenvio: um envio que falhou não é repetido. Para recuperar o que se perdeu, consulte o log ou a API.
- Após 10 falhas seguidas, o webhook é desativado e deixa de receber envios. Um envio bem-sucedido zera essa contagem. Para voltar a receber, o administrador reativa o webhook no cadastro.
- Não há garantia de ordem entre envios. Use
occurredAt,updatedAtestatuspara tratar eventos que cheguem fora de ordem.
Para não estourar o tempo de resposta, o receptor pode responder 2xx assim que validar a assinatura e processar o conteúdo depois.
Log de envios
Section titled “Log de envios”O xLumen mantém um log com o histórico de envios de cada webhook, visível para administradores. A lista mostra, para cada envio:
- o evento que originou o disparo
- o status do envio (sucesso ou erro)
- o código HTTP retornado pelo receptor
- a data do envio
Ao abrir um envio, o detalhe traz o payload enviado, a resposta do receptor e a mensagem de erro, quando houve falha.
Perguntas frequentes
Section titled “Perguntas frequentes”Quais eventos do xLumen disparam webhook?
Section titled “Quais eventos do xLumen disparam webhook?”Apenas eventos de agendamento: AGENDAMENTO_CRIADO na criação e AGENDAMENTO_ATUALIZADO em cancelamento, conclusão, reagendamento e edição de dados. Alterar campos personalizados não dispara webhook.
Como sei se um AGENDAMENTO_ATUALIZADO foi um cancelamento?
Section titled “Como sei se um AGENDAMENTO_ATUALIZADO foi um cancelamento?”Compare statusAnterior com status: ATIVO → CANCELADO indica cancelamento. O motivo, quando informado, vem em motivoCancelamento.
Por que recebi dois envios ao reagendar?
Section titled “Por que recebi dois envios ao reagendar?”O reagendamento gera um AGENDAMENTO_ATUALIZADO do agendamento original, com status REAGENDADO e reagendadoPara, e um AGENDAMENTO_CRIADO do novo agendamento, com reagendamentoDe. Os dois campos ligam um envio ao outro.
O webhook envia todos os dados do agendamento?
Section titled “O webhook envia todos os dados do agendamento?”Envia os campos listados em Corpo. idExterno, valores de campos personalizados e contato do cliente não vêm no envio; o receptor consulta a API com o id recebido.
Como saber se o webhook recebido veio mesmo do xLumen?
Section titled “Como saber se o webhook recebido veio mesmo do xLumen?”Cadastre o webhook com secret e valide o header x-xlumen-signature: calcule o HMAC-SHA256 do corpo bruto com o secret, em hexadecimal, e compare em tempo constante. Sem secret, o envio chega sem assinatura.
Perdi o secret. Como recupero?
Section titled “Perdi o secret. Como recupero?”Não é possível: depois de salvo, o secret não é exibido de novo. Troque por um novo no cadastro do webhook e atualize o sistema receptor.
O que acontece quando o endpoint receptor fica fora do ar?
Section titled “O que acontece quando o endpoint receptor fica fora do ar?”O envio que falhou não é repetido. Após 10 falhas seguidas, o webhook é desativado e precisa ser reativado pelo administrador no cadastro.
Onde acompanho o que foi enviado?
Section titled “Onde acompanho o que foi enviado?”No log de envios do webhook, visível para administradores, com evento, status, código HTTP, data e, no detalhe, o payload enviado, a resposta do receptor e o erro.