API Pública / Webhooks

Webhooks

Receba um aviso no seu sistema quando algo acontece no provedor, sem precisar ficar consultando a API.

Como funciona

  1. O provedor cadastra o endereço do seu sistema em Configurações → API pública → Webhooks e escolhe os eventos.
  2. O sistema mostra um segredo (whsec_…), que o provedor entrega a você para validar a origem dos avisos.
  3. Quando um evento acontece, o seu endereço recebe um POST com JSON, em até cerca de 30 segundos.
  4. Responda com qualquer código 2xx em até 10 segundos. Sem isso, o aviso é enviado de novo mais tarde.

Requisitos do endereço: HTTPS, público na internet, sem usuário e senha na URL. Endereços de rede interna são recusados.

O provedor pode enviar um teste a qualquer momento: ele chega como evento teste.ping.

Eventos

EventoQuandoEm dados
fatura.criadaUma fatura foi gerada.fatura
fatura.pagaUma fatura foi paga (banco, PIX ou baixa manual).fatura
fatura.vencidaUma fatura passou do vencimento sem pagamento.fatura
fatura.canceladaUma fatura foi cancelada.fatura
fatura.estornadaUm pagamento foi estornado.fatura
contrato.ativadoContrato ativado.contrato, registro
contrato.bloqueadoContrato bloqueado.contrato, registro
contrato.desbloqueadoContrato desbloqueado.contrato, registro
contrato.desbloqueio_confiancaDesbloqueio de confiança aplicado.contrato, registro
contrato.inativadoContrato inativado.contrato, registro
contrato.canceladoContrato cancelado.contrato, registro
conexao.onlineO cliente voltou a ficar online.contrato, registro
conexao.offlineO cliente ficou offline.contrato, registro
chamado.abertoUm chamado foi aberto (por qualquer canal).chamado
chamado.atualizadoUm chamado foi alterado.chamado

fatura, contrato e chamado têm exatamente o mesmo formato das rotas /faturas/{id}, /contratos/{id} e /chamados/{id}. Os eventos de conexão podem ser muitos em redes instáveis: assine só se for usar.

Formato do aviso

POST /webhooks/websystem HTTP/1.1
Content-Type: application/json
User-Agent: WebSystemProvider-Webhooks/1
X-WSP-Evento: fatura.paga
X-WSP-Entrega: 4182
X-WSP-Assinatura: t=1760367602,v1=5f1c0e...a9

{
  "id": "evt_9120",
  "tipo": "fatura.paga",
  "criado_em": "2026-10-13T15:00:02.000Z",
  "dados": {
    "fatura": {
      "id": 77, "cliente_id": 5, "contrato_id": 10, "situacao": "paga", "tipo": "pix",
      "valor": 99.9, "valor_pago": 99.9, "vencimento": "2026-10-10",
      "pago_em": "2026-10-13T14:59:40.000Z", "segunda_via": null
    }
  }
}

Nos eventos de contrato e de conexão, registro traz o que aconteceu:

"registro": { "acao": "bloqueio", "descricao": "Bloqueio automático por inadimplência", "autor": "Sistema", "ocorrido_em": "2026-10-13T10:00:00.000Z" }

Validar a assinatura

Cada aviso vem assinado. O cabeçalho X-WSP-Assinatura tem o horário (t) e a assinatura (v1): um HMAC-SHA256, com o segredo do webhook, de <t>.<corpo bruto>. Compare a assinatura calculada com a recebida e recuse avisos com mais de 5 minutos, para evitar reenvio malicioso.

Calcule sobre o corpo bruto, exatamente como chegou. Reformatar o JSON antes muda a assinatura.

Node.js (Express)

const crypto = require('crypto');
app.post('/webhooks/websystem', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('X-WSP-Assinatura') || '';
  const t = (header.match(/t=(\d+)/) || [])[1];
  const v1 = (header.match(/v1=([a-f0-9]+)/) || [])[1];
  const esperado = crypto.createHmac('sha256', process.env.WSP_WEBHOOK_SECRET)
    .update(`${t}.${req.body}`).digest('hex');
  const valido = v1 && v1.length === esperado.length
    && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(esperado));
  if (!valido || Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.sendStatus(401);

  const evento = JSON.parse(req.body);
  // trate evento.tipo e evento.dados — e responda rápido:
  res.sendStatus(200);
});

PHP

$corpo = file_get_contents('php://input');
preg_match('/t=(\d+),v1=([a-f0-9]+)/', $_SERVER['HTTP_X_WSP_ASSINATURA'] ?? '', $m);
$esperado = hash_hmac('sha256', ($m[1] ?? '') . '.' . $corpo, getenv('WSP_WEBHOOK_SECRET'));
if (empty($m) || !hash_equals($esperado, $m[2]) || abs(time() - (int)$m[1]) > 300) {
  http_response_code(401);
  exit;
}
$evento = json_decode($corpo, true);
http_response_code(200);

Python (Flask)

import hmac, hashlib, os, re, time
from flask import request, abort

@app.post('/webhooks/websystem')
def webhook():
    corpo = request.get_data()
    m = re.search(r't=(\d+),v1=([a-f0-9]+)', request.headers.get('X-WSP-Assinatura', ''))
    if not m:
        abort(401)
    esperado = hmac.new(os.environ['WSP_WEBHOOK_SECRET'].encode(), f'{m[1]}.'.encode() + corpo, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(esperado, m[2]) or abs(time.time() - int(m[1])) > 300:
        abort(401)
    evento = request.get_json()
    return '', 200

Novas tentativas

Se o seu sistema não responder 2xx em 10 segundos, o aviso é enviado de novo, com espera crescente:

TentativaEspera depois da anterior
2ª1 minuto
3ª5 minutos
4ª15 minutos
5ª1 hora
6ª3 horas
7ª6 horas
8ª12 horas
9ª (última)24 horas
  • Depois da última, a entrega fica como falhou. O provedor vê cada entrega na tela e pode reenviar.
  • Um endereço com 100 falhas seguidas é pausado automaticamente; o provedor retoma quando o seu sistema voltar.
  • Os avisos e o histórico de entregas ficam guardados por 30 dias.

Boas práticas

  • Responda rápido e processe depois (fila): o limite é de 10 segundos.
  • Evite processar duas vezes: um aviso pode chegar mais de uma vez (reenvio). Use o id do evento (evt_…) para ignorar repetidos.
  • A ordem não é garantida: use criado_em e, se precisar do estado atual, consulte a rota correspondente.
  • Os avisos começam a partir do cadastro do primeiro endereço: eventos anteriores não são enviados.