Receber eventos
Webhooks
Receba um POST no seu sistema sempre que algo acontecer no atendimento — sem ficar consultando a API.
Cadastrar#
Pelo painel, em Ajustes › Integrações › Webhooks (com histórico de entregas e botão de teste), ou pela API com Assinar eventos. Nos dois casos você escolhe a URL e os eventos, e recebe um segredo whsec_... para validar as entregas. Cada workspace tem até 50 webhooks.
Eventos#
| Evento | Quando dispara |
|---|---|
conversation.created | Uma conversa foi aberta (mensagem nova de um contato ou envio ativo). |
conversation.updated | A conversa mudou: atribuição, etiquetas, situação aberta/pendente. |
conversation.resolved | A conversa foi resolvida. |
message.created | Mensagem nova, recebida ou enviada. Notas internas não geram evento. |
contact.created | Um contato foi criado. |
contact.updated | Os dados de um contato mudaram. |
contact.opted_out | O contato foi descadastrado (pediu na conversa, pela equipe ou pela API). Dados: contact { id, name }, source (cliente | manual | api) e at. |
contact.opted_in | O descadastro foi desfeito: o contato volta a receber mensagens automáticas. Mesmos dados. |
O botão Testar do painel envia o evento webhook.test, com data.message e data.sent_at, para você validar a integração sem esperar um evento real.
Formato da entrega#
Cada entrega é um POST com JSON. O envelope é sempre o mesmo; em data vem o objeto do evento (mensagem, conversa ou contato) no formato do motor de atendimento. Exemplo resumido de message.created:
{
"id": "0b6f1c9e-4d2a-4e8b-9a31-5f7c2d1e0a44",
"event": "message.created",
"workspace_id": "3f6c2a1e-8b4d-4c1a-9e2f-7a5b0c9d1e22",
"webhook_id": "9a8b7c6d-1111-4e2f-8a3b-2c1d0e9f8a77",
"timestamp": "2026-09-25T14:03:11Z",
"data": {
"id": 9812,
"content": "Oi, quero saber do meu pedido",
"message_type": "incoming",
"private": false,
"created_at": "2026-09-25T14:03:10.512Z",
"conversation": { "id": 120, "status": "open", "inbox_id": 4 },
"sender": { "id": 35, "name": "Maria Souza", "phone_number": "+5511999990000", "type": "contact" }
}
}Só mensagens do cliente
message.created dispara também para mensagens enviadas pela equipe, chatbots e pela própria API. Para reagir só ao que o cliente mandou, filtre data.message_type igual a "incoming" (em alguns eventos vem o número 0) — e evite loops ao responder mensagens da própria integração.Headers
| Header | Conteúdo |
|---|---|
X-Solaria-Event | Nome do evento (igual a event no corpo) |
X-Solaria-Delivery | Id único da entrega (igual a id) — use para descartar repetidas |
X-Solaria-Signature | sha256= + HMAC-SHA256 do corpo bruto com o segredo do webhook |
User-Agent | SOLARIA-Webhooks/1.0 |
Validar a assinatura#
Calcule o HMAC-SHA256 do corpo exatamente como chegou usando o segredo, prefixe com sha256= e compare com o header em tempo constante. Recuse a entrega se não bater.
import crypto from "node:crypto"
import express from "express"
const app = express()
const SECRET = process.env.SOLARIA_WEBHOOK_SECRET // whsec_...
// use o corpo BRUTO: re-serializar o JSON muda os bytes e quebra a assinatura
app.post("/hooks/solaria", express.raw({ type: "application/json" }), (req, res) => {
const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex")
const got = req.get("X-Solaria-Signature") ?? ""
const ok = got.length === expected.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))
if (!ok) return res.sendStatus(401)
const event = JSON.parse(req.body)
res.sendStatus(200) // responda rápido; processe depois
console.log(event.event, event.data)
})Tentativas e prazos#
- Responda com qualquer status
2xxem até 10 segundos. Processe o evento em segundo plano se for demorar. - Em erro de rede, tempo esgotado ou status
5xx, o SOLAR.IA tenta de novo: até 3 tentativas no total, esperando 1s e depois 4s. Status4xxnão é repetido. - Uma mesma ocorrência pode chegar mais de uma vez: use
X-Solaria-Deliverypara ignorar repetidas. - Cada entrega (status, resposta, tempo e tentativas) fica no histórico do webhook no painel.
Endereços aceitos#
A URL precisa ser http:// ou https:// e responder pela internet. Por segurança, endereços internos (localhost, redes privadas) são recusados e redirecionamentos não são seguidos. Para testar localmente, use um túnel como ngrok ou Cloudflare Tunnel.