Webhooks: eventos para seu servidor
Webhooks assinados e REST hooks do reactive.chat: eventos, cabeçalhos, corpo, verificação de assinatura com HMAC-SHA256, novas tentativas, tempos limite, desativação e exemplos de verificação em PHP, Node e Python.
Os webhooks informam ao seu servidor o que acontece no chat: uma nova conversa, uma nova mensagem, uma transferência para a equipe, uma avaliação. Há duas formas:
- Webhook assinado – um endereço de destino por espaço de trabalho, configurado na conta, com cada entrega assinada com HMAC-SHA256. A opção adequada para seus próprios sistemas.
- REST hooks – destinos que uma ferramenta de automação (Zapier, Make, n8n) cadastra e remove por conta própria pela API do servidor.
Eventos
- conversation.created – uma conversa foi criada. O canal está em
channel(web,whatsapp,email…). - message.created – uma mensagem foi armazenada (do visitante, do assistente, de um atendente ou do sistema).
- handoff.requested – a conversa deve ser transferida para uma pessoa; com
reasonetarget. - csat.submitted – o visitante deu uma avaliação;
score(1ou-1) ecomment(com dados pessoais ocultos). - fehlerfall.erstellt – um novo caso de erro de JavaScript foi detectado no seu site (veja o Manual).
As conversas da pesquisa no site (Smart Answer) só disparam eventos quando se tornam visíveis como chat.
Configurar o webhook assinado
Na conta, em Configurações → Integrações → Webhooks (permissão: gerenciar integrações):
- URL –
https://, no máximo 1.000 caracteres, acessível publicamente. Um endereço por espaço de trabalho. - Eventos – uma seleção; nenhuma seleção significa todos.
- Segredo de assinatura – exibido na página (64 caracteres hexadecimais). Gerar novamente invalida o anterior imediatamente; planeje a troca para que seu receptor aceite ambos por um breve período.
Nenhuma chamada de teste é enviada ao salvar. Depois de ativar, pode levar cerca de 15 segundos para os eventos começarem a chegar; eventos anteriores à ativação não são entregues retroativamente.
A entrega
POST /rc-webhook HTTP/1.1
Host: hooks.your-company.com
Content-Type: application/json; charset=utf-8
User-Agent: reactive.chat-webhook/1.0
X-RC-Event: csat.submitted
X-RC-Delivery: 01JAB3C4D5E6F7G8H9JKMNPQRS
X-RC-Timestamp: 1792503600
X-RC-Signature: sha256=3f0a1c...
{"id":"01JAB3C4D5E6F7G8H9JKMNPQRS","event":"csat.submitted","ts":1792503600,"site_key":"ABCDEFGHJKLMNPQRSTUVWX12","conversation_id":"01JAB2...","session_id":"s_...","visitor_id":"v_...","data":{"score":5,"comment":"Very helpful"}}
Cabeçalhos:
- X-RC-Event – nome do evento.
- X-RC-Delivery – ID da entrega; permanece igual nas novas tentativas. Use-o para detectar duplicatas.
- X-RC-Timestamp – segundos Unix.
- X-RC-Signature –
sha256=+hex(HMAC_SHA256(secret, X-RC-Timestamp + "." + raw_body)). A chave é a própria string hexadecimal exibida.
Corpo:
- id (string) – igual a
X-RC-Delivery. - event (string).
- ts (inteiro) – segundos Unix.
- site_key, conversation_id, session_id, visitor_id (string ou
null). - data (objeto) – dados específicos do evento. Para
fehlerfall.erstellt:fall_id,project_id,signatur,meldung,datei,zeile,spalte,seite,browser,os,stack,erste_ms,url(link para o caso na conta). Paramessage.created:messagecomid,seq,kind,direction,author_type,author_name(somente para atendente e assistente),via,body(com dados sensíveis ocultos no servidor),redacted,ts(milissegundos). Parahandoff.requested:reasonetarget. Paracsat.submitted:scoreecomment. Paraconversation.created:channel.
Verificar a assinatura
Verifique antes de qualquer processamento: timestamp com no máximo 300 segundos de idade, assinatura sobre o corpo bruto, comparação em tempo constante.
PHP:
<?php
$geheim = getenv('RC_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_RC_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_RC_SIGNATURE'] ?? '';
if (!ctype_digit($ts) || abs(time() - (int) $ts) > 300) { http_response_code(401); exit; }
$soll = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $geheim);
if (!hash_equals($soll, $sig)) { http_response_code(401); exit; }
$ereignis = json_decode($body, true);
// detect duplicates: remember $ereignis['id']
http_response_code(204);
Node.js (Express):
const crypto = require('crypto');
app.post('/rc-webhook', express.raw({ type: 'application/json' }), (req, res) => {
const ts = req.get('X-RC-Timestamp') || '';
const sig = req.get('X-RC-Signature') || '';
if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(401);
const soll = 'sha256=' + crypto.createHmac('sha256', process.env.RC_WEBHOOK_SECRET)
.update(ts + '.' + req.body.toString('utf8')).digest('hex');
if (sig.length !== soll.length || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(soll))) {
return res.sendStatus(401);
}
const ereignis = JSON.parse(req.body.toString('utf8'));
res.sendStatus(204);
verarbeiteSpaeter(ereignis); // respond quickly, work afterwards
});
Python (Flask):
import hmac, hashlib, os, time
from flask import request, abort
@app.post("/rc-webhook")
def rc_webhook():
ts = request.headers.get("X-RC-Timestamp", "")
sig = request.headers.get("X-RC-Signature", "")
body = request.get_data() # raw bytes
if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
abort(401)
soll = "sha256=" + hmac.new(os.environ["RC_WEBHOOK_SECRET"].encode(),
ts.encode() + b"." + body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(soll, sig):
abort(401)
return "", 204
Respostas, novas tentativas e desativação
Responda rapidamente com um código 2xx e processe depois. Tempos limite: 4 segundos para estabelecer a conexão, 10 segundos no total. Redirecionamentos não são seguidos.
- 2xx – entregue.
- 408, 429, 5xx, tempo limite, erro de rede – nova tentativa após 1 s, 5 s, 25 s, 125 s e 625 s (até seis tentativas no total); depois, o evento é descartado.
- 410 Gone – a integração é desativada. Use 410 somente se realmente não quiser mais receber webhooks.
- outros 4xx – sem nova tentativa.
Eventos com mais de uma hora não são mais entregues. Se os erros se acumularem (5 em 60 segundos), a entrega ao seu destino será pausada por 30 segundos; as tarefas serão tentadas novamente depois. Um chat nunca espera pelo seu webhook.
A ordem das entregas não é garantida. Se necessário, ordene por ts ou pelo seq das mensagens da API.
REST hooks (Zapier, Make, n8n)
As ferramentas de automação cadastram seus próprios destinos pela API do servidor. Você também pode usar isso diretamente:
curl -X POST https://reactive.chat/v1/hooks \
-H "Authorization: Bearer rc_live_..." \
-H "Content-Type: application/json" \
-d '{"event":"message.created","target_url":"https://hooks.your-company.com/rc","source":"n8n"}'
- POST /v1/hooks – um escopo com permissão de escrita.
event(um dos eventos acima),target_url(https://, no máximo 500 caracteres, público),source(zapier,makeoun8n, padrãozapier). Resposta 201 comid. A mesma combinação de evento e destino não é criada duas vezes. No máximo 200 assinaturas por espaço de trabalho. - GET /v1/hooks – lista com
id,event,target_url,source,failures,created_at,last_ok_at. - DELETE /v1/hooks/{id} – cancelar a assinatura.
Entrega de um REST hook:
POST https://hooks.your-company.com/rc
Content-Type: application/json
User-Agent: reactive.chat-n8n/1.0
X-RC-Event: message.created
X-RC-Delivery: 01JAB3...
{"event":"message.created","delivery_id":"01JAB3...","data":{"name":"message.created","ts":1792503600123,"site_key":"...","conversation_id":"...","session_id":"...","visitor_id":"...","message":{"id":"...","seq":7,"kind":"text","direction":"in","author_type":"bot","author_name":"Assistant","via":null,"body":"…","redacted":false,"ts":1792503600120}}}
- Sem assinatura. Use um endereço de destino difícil de adivinhar e verifique
X-RC-Deliverypara detectar duplicatas. Para seus próprios sistemas, o webhook assinado é a melhor opção. dataé o evento completo: inclui tambémworkspace_id,delivery_ideattempt(número da tentativa). Aqui,data.tsestá em milissegundos.- O
datademessage.createdcontémmessagecomid,seq,kind,direction,author_type,author_name(somente para atendente e assistente),via,body(com dados sensíveis ocultos no servidor),redacted,ts.handoff.requestedincluireasonetarget;csat.submitted,scoreecomment;conversation.created,channel. - Um 410 cancela apenas essa assinatura. Hooks de uma chave de API revogada deixam de receber entregas.
- No estado atual, a entrega exige que a integração correspondente (Zapier, Make ou n8n) esteja ativada no espaço de trabalho em Integrações.
O que não existe
- Não há webhook de entrada pelo qual seu servidor envie mensagens de visitantes para uma conversa. Você envia respostas em nome da sua equipe pela API do servidor ou pelo MCP.
- Não há período de transição ao renovar o segredo de assinatura.
- Não há evento quando uma conversa é excluída.
A seguir: API do servidor
Ìgbà àtúnṣe tó kẹ́yìn: