# Webhooks

A emissão, o cancelamento e a substituição são assíncronos. O webhook avisa o seu sistema quando a Sefin Nacional autoriza, rejeita, cancela ou substitui a nota, e quando o certificado de um contribuinte está para vencer.

## Eventos

| Tipo | Quando |
| --- | --- |
| `invoice.authorized` | A Sefin Nacional autorizou a NFS-e. |
| `invoice.rejected` | A Sefin Nacional rejeitou a nota, ou o Nota4You recusou antes do envio (por exemplo, certificado vencido). |
| `invoice.cancelled` | A Sefin Nacional registrou o cancelamento da nota. |
| `invoice.substituted` | A Sefin Nacional autorizou a nota substituta. O corpo traz a nota original, com `substitute_invoice_id`. |
| `invoice_event.rejected` | A Sefin Nacional recusou um cancelamento. O corpo traz `data.invoice_event` com os códigos em `rejection_codes`. |
| `certificate.expiring` | O certificado de um contribuinte vence em 30, 15, 7, 1 ou 0 dias. Um evento por certificado e por dia de aviso. |
| `webhook.test` | Você pediu um envio de teste. |

## Cadastro

Em Configurações > Webhooks ou pela API (`/api/v1/webhook_endpoints`, escopo `webhooks:manage`): endereço `https`, eventos, descrição e a opção `include_personal_data`. Até 10 endereços por conta. O segredo (`whsec_...`) aparece só na criação e na troca. Na tela de cada webhook você vê as entregas, envia um teste e reenvia uma entrega.

O esquema de cada corpo está na seção `webhooks` do [OpenAPI](/openapi.json).

## Corpo

```json
{
  "id": "0f6b2c1e-8f1d-4f7a-9a51-2a3c9b0e7d11",
  "type": "invoice.authorized",
  "created_at": "2026-10-19T14:32:05-03:00",
  "data": {
    "invoice": {
      "id": "4a77...", "taxpayer_id": "9c1e...", "status": "authorized",
      "access_key": "3550308...", "nfse_number": "42", "rejection_codes": [],
      "customer": { "id": "8bb9..." }
    }
  }
}
```

Sem a opção `include_personal_data`, o corpo não traz dados pessoais: o tomador vai só com o `id`. Para os detalhes, consulte `GET /api/v1/invoices/:id`.

## Dados pessoais

Com `include_personal_data: true`, o corpo traz `customer` e, quando a nota tiver, `recipient` e `intermediary`, cada um com `document_type`, `document`, `name`, `email`, `phone` e `address`. No `certificate.expiring`, a opção acrescenta `subject_document` (o CNPJ ou CPF do contribuinte). Ligue só se o seu sistema precisar desses dados e tiver base legal para recebê-los (LGPD). Veja [Privacidade](/docs/privacidade).

## Certificado vencendo

```json
{
  "id": "5c1d...",
  "type": "certificate.expiring",
  "created_at": "2026-12-11T06:00:00-03:00",
  "data": {
    "certificate": {
      "id": "7645...", "taxpayer_id": "fc3c...",
      "not_after": "2027-01-10T12:00:00-03:00", "days_left": 30
    }
  }
}
```

## Assinatura

Cada envio traz o cabeçalho `X-Nota4you-Signature: t=<segundos>,v1=<hex>`, onde `v1` é `HMAC-SHA256(segredo, "<t>.<corpo bruto>")`. Confira antes de confiar no corpo e recuse envios com mais de 5 minutos:

```ruby
timestamp, signature = header.scan(/t=(\d+),v1=(\h+)/).first
expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{raw_body}")
valid = ActiveSupport::SecurityUtils.secure_compare(expected, signature) &&
        Time.at(timestamp.to_i) > 5.minutes.ago
```

Outros cabeçalhos: `X-Nota4you-Event-Id`, `X-Nota4you-Event-Type`, `X-Nota4you-Delivery-Attempt`.

## Novas tentativas

Uma resposta `2xx` conclui o envio. Redirecionamentos contam como falha. Tempo limite: 5 s para conectar, 10 s para responder. Depois de uma falha, tentamos de novo em 1 min, 5 min, 30 min, 1 h, 2 h, 4 h, 8 h e 8 h. Todos os envios de um evento têm o mesmo `id`: use-o para ignorar repetições.
