Pular para o conteúdo

Webhooks de saída e assinatura

Webhook é como a FORSI avisa o seu sistema quando algo acontece (mensagem recebida, status de um envio, negócio movido, compromisso marcado) sem você precisar perguntar. Cadastre um endereço https público (pela tela ou por POST /webhooks) e escolha os eventos. A cada evento, mandamos um POST com JSON para o seu endereço, assinado com HMAC-SHA256.

  • Responda 2xx em até 10 segundos; outra resposta ou demora conta como falha. Não seguimos redirecionamentos.
  • O endereço precisa ser https, porta 443 e um nome de domínio público (sem IP, sem rede interna).
  • Até 10 webhooks por organização. Downgrade que tira a API do plano também para os webhooks.

Eventos#

EventoQuando
mensagem.recebidaO contato mandou uma mensagem.
mensagem.enviadaUma mensagem sua saiu (pela tela, por um agente ou pela API).
mensagem.statusMudou o status de uma mensagem enviada: entregue, lida, falhou...
conversa.criadaNasceu uma conversa nova.
conversa.atribuidaA conversa mudou de responsável ou de equipe.
conversa.encerradaA conversa foi resolvida.
contato.criadoUm contato novo foi criado.
negocio.criadoUm negócio novo entrou no funil.
negocio.movidoUm negócio mudou de etapa.
negocio.ganhoUm negócio foi marcado como ganho.
negocio.perdidoUm negócio foi marcado como perdido.
agendamento.criadoUm compromisso foi marcado (pela tela, pelo agente ou pela API).
agendamento.remarcadoUm compromisso mudou de dia, hora ou agenda. Vem com o horário anterior.
agendamento.canceladoUm compromisso foi cancelado.
agendamento.confirmadoUm compromisso foi confirmado.
membro.convidadoAlguém foi convidado para a equipe (pela tela ou pela API).
membro.entrouUma pessoa aceitou o convite e entrou na equipe.
membro.alteradoMudou o papel ou as permissões de alguém. Vem com o valor anterior.
membro.removidoUma pessoa saiu da equipe.

Todos têm o mesmo envelope: id (o mesmo em todas as tentativas), evento, criado_em, organizacao_id e dados. Em dados vem o que importa ao evento: mensagem, conversa, contato, negocio, agendamento, membro, convite... Mudanças trazem estado_anterior ou anterior (agendamento.remarcado e membro.alterado).

corpo de mensagem.recebida (resumido)
{
  "id": "5e458e2a-2f89-4c56-8f5e-f6bfa9ef0a35",
  "evento": "mensagem.recebida",
  "criado_em": "2026-10-04T13:05:12.481Z",
  "organizacao_id": "dddecb67-6b55-43da-94f6-18c8d6680571",
  "dados": {
    "mensagem": {
      "id": "4e8cb5d0-2d89-4fc2-93bb-c8e5e17d4629",
      "conversa_id": "495a7cae-128c-4b66-adbc-807b3959c129",
      "direcao": "entrada",
      "tipo": "texto",
      "texto": "Oi, queria remarcar minha consulta",
      "autor_tipo": "contato",
      "criado_em": "2026-10-04T13:05:11.902Z"
    },
    "conversa": {
      "id": "495a7cae-128c-4b66-adbc-807b3959c129",
      "estado": "aberta",
      "caixa_id": "7a9d53e9-9d1b-4122-923b-fac67958f445"
    },
    "contato": {
      "id": "08054a7f-a752-481e-9915-cf4cf944df39",
      "nome": "Maria Souza",
      "telefone": "+5531988887777"
    }
  }
}

Cabeçalhos#

  • X-Forsi-Event: o evento.
  • X-Forsi-Delivery: id da entrega (muda no reenvio manual).
  • X-Forsi-Timestamp: segundos Unix do envio.
  • X-Forsi-Signature: t=<timestamp>,v1=<hex>. Durante a troca de segredo vêm dois v1.
  • User-Agent: começa com FORSI-Chat-Webhooks/1.

Conferir a assinatura#

Cada webhook tem um segredo (whsec_...), mostrado uma vez quando é criado ou trocado. v1 é o HMAC-SHA256, em hexadecimal, do texto <timestamp>.<corpo cru>, usando como chave o segredo inteiro, com o prefixo `whsec_`.

Como conferir a assinatura de um webhookA FORSI calcula o HMAC-SHA256 de "<t>.<corpo cru>" com o segredo whsec_ inteiro e manda no cabeçalho X-Forsi-Signature (t=<timestamp>,v1=<hex>). O seu servidor lê o corpo cru, recusa se t estiver a mais de 5 minutos do relógio, refaz o HMAC com o mesmo segredo e compara em tempo constante com cada v1. Bateu: processe e responda 2xx em até 10 segundos. Não bateu: responda 401 e descarte.A FORSI assina cada entregaHMAC-SHA256(segredo, "t.corpo")X-Forsi-Signature: t=…,v1=…POST na sua URLO seu servidor confere1. lê o corpo cru, antes do JSON.parse2. recusa se t tiver mais de 5 minutos3. refaz o HMAC com o mesmo segredo4. compara com cada v1 em tempo constantebateunão bateuProcesseresponda 2xxem até 10 segundosRecuseresponda 401e descarte o evento
Como conferir a assinatura de um webhook
  1. Leia o corpo cru, byte a byte, antes de converter para JSON (reformatar muda a assinatura).
  2. Pegue t e os v1 do cabeçalho X-Forsi-Signature.
  3. Recuse se t estiver a mais de 5 minutos do seu relógio (protege contra reenvio de um evento capturado).
  4. Calcule o HMAC e compare em tempo constante com cada v1. Aceite se qualquer um bater.

curl + openssl (testar o seu receptor)

# Simula uma entrega assinada para testar o seu receptor (mesma conta que a FORSI faz).
SEGREDO="whsec_EXAMPLE"   # o segredo do webhook, inteiro, com o prefixo
T=$(date +%s)
CORPO='{"id":"9c1d2e3f-4a5b-4c6d-8e7f-0a1b2c3d4e5f","evento":"mensagem.recebida","criado_em":"2026-10-04T13:05:12.481Z","dados":{}}'

V1=$(printf '%s.%s' "$T" "$CORPO" | openssl dgst -sha256 -hmac "$SEGREDO" | sed 's/^.*= //')

curl -X POST "https://seu-sistema.com.br/webhooks/forsi" \
  -H "Content-Type: application/json" \
  -H "X-Forsi-Event: mensagem.recebida" \
  -H "X-Forsi-Delivery: 5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e" \
  -H "X-Forsi-Timestamp: $T" \
  -H "X-Forsi-Signature: t=$T,v1=$V1" \
  --data-raw "$CORPO"

Tentativas, ordem e repetição#

  • Cada entrega é aceita no máximo uma vez. Se o seu endereço responder erro, demorar demais depois de receber ou cair no meio, a entrega fica como "resultado incerto" e não é repetida, para nunca duplicar um efeito no seu sistema. Só é tentada de novo (30 s, 2 min, 10 min, 30 min, 1 h, 3 h, 6 h e 12 h) o que comprovadamente não foi aceito: falha antes do envio (DNS, conexão recusada, certificado, conexão que não abre) ou resposta 429 / 503 do seu servidor. Outros erros 5xx não são repetidos, pois podem ter sido processados.
  • Uma entrega que nunca chegou a sair (por exemplo, cancelada quando o webhook foi desligado, ou que esgotou as tentativas sem conseguir conectar) pode ser reenviada pela tela ou por POST /webhooks/entregas/{id}/reenviar. A que já saiu uma vez é recusada com o código entrega_ja_despachada.
  • Um webhook que falha por 24 horas seguidas é desligado e o dono recebe um aviso. Reative com PATCH /webhooks/{id} e {"ativo": true}.
  • A ordem de chegada não é garantida e o mesmo evento pode chegar mais de uma vez. Use o id do evento para ignorar repetidos e o criado_em para ordenar.
  • POST /webhooks/{id}/testar manda um evento de teste assinado; GET /webhooks/{id}/entregas mostra as últimas entregas, com status e resposta.

Trocar o segredo#

POST /webhooks/{id}/rotacionar gera um segredo novo (mostrado uma vez). O antigo continua assinando junto pelas horas que você escolher (0 a 168, padrão 24): nesse tempo vêm dois v1. Atualize o segredo no seu receptor dentro desse prazo.

Perguntas frequentes#

Por que a assinatura não bate?#

Quase sempre é o corpo: o receptor converteu para JSON e de volta para texto antes de calcular, ou o framework já tinha lido o corpo. Calcule o HMAC sobre o corpo cru, exatamente como chegou. As outras causas comuns são usar o segredo sem o prefixo whsec_ e comparar com o cabeçalho inteiro em vez de cada v1.

O mesmo evento pode chegar duas vezes?#

Pode. Guarde o id do evento e ignore os repetidos. A ordem de chegada também não é garantida: ordene pelo criado_em.

Meu servidor ficou fora do ar. Perdi os eventos?#

O que não chegou a conectar (DNS, conexão recusada, certificado) é tentado de novo em 30 s, 2 min, 10 min, 30 min, 1 h, 3 h, 6 h e 12 h. Responder 503 enquanto o servidor se recupera também faz a FORSI tentar depois. Veja o que chegou em GET /webhooks/{id}/entregas.

Quanto tempo tenho para responder?#

10 segundos. Responda 2xx logo e faça o trabalho pesado depois, numa fila sua.