# Avisos ao seu sistema (webhooks)

Receba cobranca.paga e cobranca.devolvida no seu sistema com avisos assinados (HMAC-SHA256), novas tentativas automáticas e exemplos de verificação.

Quando uma cobrança é paga (ou uma devolução termina), a SinapsePay envia um `POST` JSON para a URL que você cadastrou no painel, em **Contas → Avisos ao seu sistema**. A URL é por conta bancária; o segredo da assinatura (`whsec_…`) aparece só ao configurar e ao trocar.

## Eventos

| `evento` | Quando |
|---|---|
| `cobranca.paga` | O pagamento chegou e a cobrança ficou `paga` |
| `cobranca.devolvida` | Uma devolução foi concluída (total ou parcial) |
| `cobranca.devolucao_recusada` | O banco recusou uma devolução |

## O que chega

```http
POST /seu/endpoint
Content-Type: application/json
User-Agent: SinapsePay-Webhook/1
X-SinapsePay-Evento: cobranca.paga
X-SinapsePay-Entrega: 27708324-70d6-469b-a3fc-73304e527493
X-SinapsePay-Assinatura: t=1791251108,v1=87e8f770150cad1ad168c0fe3b26009da473749717181896884a1203e353caee
```

```json
{
  "id": "27708324-70d6-469b-a3fc-73304e527493",
  "evento": "cobranca.paga",
  "criadoEm": "2026-10-06T01:45:08.381715Z",
  "cobranca": {
    "id": "4ec2604f-97c2-4715-9642-c6e59cb7bef8",
    "idExterno": "pedido-1042",
    "produto": "pix",
    "status": "paga",
    "valor": 12000,
    "pagoEm": "2026-10-06T01:45:08.334Z",
    "pagamentos": [
      {
        "valorPago": 12000,
        "pagoEm": "2026-10-06T01:45:08.334Z",
        "endToEndId": "E75691000179125110833465113",
        "txid": "4ec2604f97c247159642c6e59cb7bef8",
        "pagoPorNome": "Maria Souza",
        "pagoPorDocumento": "52998224725"
      }
    ]
  }
}
```

Nos eventos de devolução, `cobranca.devolucoes` traz a lista com a situação de cada uma.

## Conferir a assinatura

1. Separe `t` e `v1` do cabeçalho `X-SinapsePay-Assinatura`.
2. Calcule `HMAC-SHA256(segredo, t + "." + corpo)` em hexadecimal, com o **corpo exatamente como chegou** (antes de converter o JSON).
3. Compare com `v1` em tempo constante e recuse `t` com mais de 5 minutos de diferença do seu relógio.

```js
// Node.js
import { createHmac, timingSafeEqual } from 'node:crypto'

export function avisoValido(corpoCru, cabecalho, segredo) {
  const { t, v1 } = Object.fromEntries(cabecalho.split(',').map((parte) => parte.split('=')))
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
  const esperado = createHmac('sha256', segredo).update(`${t}.${corpoCru}`).digest('hex')
  return v1?.length === esperado.length && timingSafeEqual(Buffer.from(v1), Buffer.from(esperado))
}
```

```python
# Python
import hashlib, hmac, time

def aviso_valido(corpo_cru: bytes, cabecalho: str, segredo: str) -> bool:
    partes = dict(p.split("=", 1) for p in cabecalho.split(","))
    if abs(time.time() - int(partes["t"])) > 300:
        return False
    esperado = hmac.new(segredo.encode(), f'{partes["t"]}.'.encode() + corpo_cru, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperado, partes.get("v1", ""))
```

## Responda rápido e ignore repetidos

- Responda **2xx em até 10 segundos** e processe depois, se precisar. Redirecionamentos não são seguidos.
- Sem 2xx, a SinapsePay tenta de novo em **30 s, 2 min, 10 min, 30 min, 1 h, 3 h, 6 h e 12 h** (cerca de 22 horas) e depois desiste. O histórico de entregas fica no painel.
- O mesmo aviso pode chegar mais de uma vez: use `X-SinapsePay-Entrega` (igual ao `id` do corpo, o mesmo em todas as tentativas) para ignorar repetidos.
- A URL precisa ser `https` e apontar para a internet: endereços de rede interna são recusados.

Perdeu um aviso? [Consulte a cobrança](/docs/cobrancas#consultar): a consulta pergunta ao banco e responde atualizada.