# Webhooks

Os webhooks notificam a sua aplicação, em tempo real, sobre o desfecho das operações. Você registra uma URL, escolhe os eventos e recebe um POST assinado a cada ocorrência. Todos os endpoints são escopados às credenciais do parceiro, e cada entrega carrega o `accountId` da conta envolvida, para você rotear internamente.

## Eventos disponíveis

Todos os eventos abaixo são de fato emitidos pela plataforma. O catálogo em `GET /webhooks/events` devolve exatamente esta lista.

### PIX

| Evento | Quando dispara |
|---|---|
| `pix.charge.created` | Cobrança ou QR criado |
| `pix.charge.paid` | Cobrança paga, PIX de entrada creditado |
| `pix.payout.queued` | PIX de saída enfileirado para processamento |
| `pix.payout.processing` | PIX de saída em processamento (aceite intermediário) |
| `pix.payout.confirmed` | PIX de saída liquidado |
| `pix.payout.failed` | PIX de saída rejeitado ou expirado |
| `pix.payout.returned` | PIX de saída devolvido |
| `pix.refund.requested` | Devolução de PIX solicitada |
| `pix.refund.completed` | Devolução de PIX concluída |
| `pix.refund.failed` | Devolução de PIX rejeitada |
| `pix.return.received` | Devolução recebida |
| `pix.received` | PIX recebido na conta, sem cobrança emitida |

### Chaves PIX

| Evento | Quando dispara |
|---|---|
| `pix.key.registered` | Chave PIX registrada |
| `pix.key.deleted` | Chave PIX excluída |
| `pix.key.blocked` | Chave PIX bloqueada |
| `pix.key.unblocked` | Chave PIX desbloqueada |

### Portabilidade

| Evento | Quando dispara |
|---|---|
| `pix.claim.created` | Reivindicação de portabilidade criada |
| `pix.claim.acknowledged` | Reivindicação reconhecida |
| `pix.claim.confirmed` | Reivindicação confirmada |
| `pix.claim.cancelled` | Reivindicação cancelada |
| `pix.claim.completed` | Reivindicação concluída |

### TED e transferências

| Evento | Quando dispara |
|---|---|
| `ted.confirmed` | TED liquidada |
| `ted.failed` | TED rejeitada ou expirada |
| `ted.received` | TED recebida na conta |
| `ted.refund.requested` | Devolução de TED recebida solicitada |
| `ted.refund.completed` | Devolução de TED recebida liquidada |
| `ted.refund.failed` | Devolução de TED recebida rejeitada |
| `transfer.confirmed` | Transferência interna concluída |
| `transfer.failed` | Transferência interna falhou |
| `transfer.received` | Transferência interna recebida |

### Infrações (MED)

| Evento | Quando dispara |
|---|---|
| `pix.infraction.created` | Infração/MED aberta sobre uma conta |
| `pix.infraction.resolved` | Infração analisada e resolvida |
| `pix.infraction.defense_submitted` | Defesa da infração enviada pelo cliente |
| `pix.med.created` | Intervenção MED aberta pela Partner API |
| `pix.med.completed` | Recuperação MED concluída, fundos devolvidos |
| `pix.med.cancelled` | Intervenção MED cancelada |

### Tarifas

| Evento | Quando dispara |
|---|---|
| `fee.charged` | Tarifa cobrada de uma conta |

### Contas

| Evento | Quando dispara |
|---|---|
| `account.created` | Conta criada |
| `account.blocked` | Conta bloqueada |
| `account.unblocked` | Conta desbloqueada |
| `account.closed` | Conta encerrada |

### Teste

| Evento | Quando dispara |
|---|---|
| `webhook.test` | Entrega de teste manual |

Os eventos de sucesso (`pix.charge.paid`, `pix.payout.confirmed`, `pix.refund.completed`, `pix.return.received`) só são entregues depois que a operação está liquidada.

## Registrar um webhook

```http
POST /api/partner/v1/webhooks
Authorization: Bearer {{access_token}}
Content-Type: application/json

{
  "url": "https://sua-aplicacao.com.br/webhooks/monetarie",
  "events": ["pix.payout.confirmed", "pix.payout.failed", "pix.charge.paid"],
  "description": "Integração principal",
  "is_active": true
}
```

- **Escopo:** `webhook:write` · **Obrigatórios:** `url`, `events`

Resposta `201`:

```json
{
  "data": {
    "id": "0f7f279c-217b-4ba2-9a96-27d7401dfe2f",
    "url": "https://sua-aplicacao.com.br/webhooks/monetarie",
    "events": ["pix.payout.confirmed", "pix.payout.failed", "pix.charge.paid"],
    "description": "Integração principal",
    "is_active": true,
    "created_at": "2026-07-10T13:00:00Z",
    "updated_at": "2026-07-10T13:00:00Z"
  }
}
```

::: warning Segredo de assinatura
O cadastro **não** devolve o segredo de assinatura. Para obter (ou trocar) o segredo, chame `POST /webhooks/:id/rotate-secret`, que devolve o segredo em texto puro **uma única vez**. Guarde-o com segurança.
:::

A URL precisa ser pública, `http` ou `https`, e não pode apontar para endereços privados ou internos.

## Obter e rotacionar o segredo

```http
POST /api/partner/v1/webhooks/{id}/rotate-secret
Authorization: Bearer {{access_token}}
```

- **Escopo:** `webhook:write`

```json
{ "message": "Secret rotacionado com sucesso", "webhook_id": "0f7f...", "new_secret": "3e071f7ee1ef00db1178a96b65476db0..." }
```

O `new_secret` é exibido só nesta resposta. Uma nova rotação invalida o anterior.

## Gerenciar webhooks

```http
GET    /api/partner/v1/webhooks              # listar (webhook:read)
GET    /api/partner/v1/webhooks/{id}         # detalhar (webhook:read)
PUT    /api/partner/v1/webhooks/{id}         # atualizar url/events/description/is_active (webhook:write)
DELETE /api/partner/v1/webhooks/{id}         # remover, responde 204 (webhook:write)
POST   /api/partner/v1/webhooks/{id}/test    # enviar entrega de teste (webhook:write)
POST   /api/partner/v1/webhooks/{id}/replay  # reenviar a última entrega (webhook:write)
GET    /api/partner/v1/webhooks/events       # catálogo de eventos (webhook:read)
```

`replay` responde `404` quando ainda não há nenhuma entrega para reenviar.

## Formato da entrega

Cada evento chega como um `POST` na URL configurada. O corpo é o JSON do evento, plano (sem envelope) e com as chaves em `camelCase`, e os metadados vão nos headers:

| Header | Descrição |
|---|---|
| `X-Monetarie-Event-Type` | Tipo do evento, por exemplo `pix.payout.confirmed` |
| `X-Monetarie-Event-Id` | Identificador da entrega, para deduplicação |
| `X-Monetarie-Timestamp` | Instante da assinatura, em segundos Unix |
| `X-Monetarie-Signature` | Assinatura `sha256=<hex>` |
| `User-Agent` | `Monetarie-Webhook/1.0` |

Identifique o evento pelo header `X-Monetarie-Event-Type`; o corpo não repete o tipo. Deduplique pelo `X-Monetarie-Event-Id`, porque a mesma entrega pode chegar mais de uma vez.

## Payload de cada evento

Todos os corpos usam chaves em `camelCase` e todos os valores monetários estão em **centavos**. Os campos abaixo são os que a sua integração precisa; um evento pode trazer campos informativos adicionais, então localize sempre pela chave, nunca pela posição.

### PIX de saída

Vale para `pix.payout.queued`, `pix.payout.processing`, `pix.payout.confirmed` e `pix.payout.failed`. O que muda entre eles é o `status`.

```json
{
  "accountId": 1042,
  "transactionId": "PIX20260711a1b2c3d4e5f6",
  "endToEndId": "E4602656220260711100000abcdef123",
  "amount": 25000,
  "status": "settled",
  "errorReason": null
}
```

| Evento | `status` |
|---|---|
| `pix.payout.queued` | `queued` |
| `pix.payout.processing` | `processing` |
| `pix.payout.confirmed` | `settled` |
| `pix.payout.failed` | `rejected`, com o motivo em `errorReason` |

### Devolução de PIX de saída

`pix.payout.returned` avisa que um PIX que você enviou foi devolvido pelo recebedor. Traz o valor devolvido em `amount` (pode ser parcial) e `status: "returned"`, com o `endToEndId` da operação original. `returnId` é o identificador da devolução no arranjo PIX.

```json
{
  "accountId": 1042,
  "transactionId": "PIXOUT20260718a1b2c3d4e5f6a7b8c9d0",
  "endToEndId": "E4602656220260711100000abcdef123",
  "returnId": "D46026562202607181015aabbccddeef",
  "amount": 25000,
  "reason": "Devolução solicitada pelo pagador",
  "status": "returned"
}
```

### Devolução solicitada, concluída e rejeitada

`pix.refund.requested` é emitido quando você solicita a devolução de um PIX recebido. O campo `endToEndId` carrega o identificador da transação original que está sendo devolvida.

```json
{
  "accountId": 1042,
  "transactionId": "PIXRET20260711aa11bb22",
  "endToEndId": "PIXIN20260710090000ffee00112",
  "amount": 25000,
  "status": "requested"
}
```

`pix.refund.completed` tem o mesmo formato do PIX de saída, com `status: "settled"`, quando a devolução é liquidada. `pix.refund.failed` avisa que a devolução foi rejeitada: o valor permanece na conta e volta a compor o restante devolvível, com o motivo em `errorReason`. Nos dois desfechos, correlacione pela chave `transactionId`, que é o `refundId` devolvido no aceite da devolução; `endToEndId` pode vir nulo.

```json
{
  "accountId": 1042,
  "transactionId": "PIXRET20260711aa11bb22",
  "endToEndId": null,
  "amount": 25000,
  "status": "rejected",
  "errorReason": "Devolução rejeitada pela instituição do pagador original",
  "recipient": null
}
```

### Cobrança criada

`pix.charge.created` confirma a criação de uma cobrança ou QR. Traz o BR Code pronto para pagamento em `brcode`.

```json
{
  "accountId": 1042,
  "txId": "monetarie-7f3a1c9e4b",
  "amount": 15000,
  "status": "active",
  "brcode": "00020101021226880014br.gov.bcb.pix...6304AB12",
  "expiresAt": "2026-07-11T11:00:00Z"
}
```

### PIX recebido

`pix.charge.paid` confirma o pagamento de uma cobrança sua. Correlacione pelo `txId` da cobrança e pelo `externalId`, quando informado na criação.

```json
{
  "accountId": 1042,
  "entityId": 987,
  "txId": "monetarie-7f3a1c9e4b",
  "externalId": "pedido-4210",
  "endToEndId": "E4602656220260711100500aa00bb11c",
  "amount": 15000,
  "description": "Pedido 4210",
  "paidAt": "2026-07-11T10:05:00Z"
}
```

### Devolução recebida

`pix.return.received` avisa que uma devolução caiu na conta. Pode ser parcial, sinalizado por `isPartial`.

```json
{
  "accountId": 1042,
  "endToEndId": "E4602656220260710090000ffee00112",
  "amount": 12000,
  "originalAmount": 25000,
  "refundedAmount": 12000,
  "isPartial": true,
  "returnReason": "MD06",
  "status": "settled"
}
```

### PIX recebido sem cobrança

`pix.received` avisa um PIX que caiu na conta sem uma cobrança emitida, como uma transferência direta para uma chave da conta. Quando o crédito vem de uma cobrança sua, o evento é `pix.charge.paid`.

```json
{
  "accountId": 10024270,
  "amount": 454,
  "endToEndId": "E4602656220260714210000aabbccdd0",
  "payerName": "MARIA DA SILVA",
  "payerIspb": "00416968",
  "payerDocument": "12345678901",
  "receivedAt": "2026-07-14T21:00:00Z"
}
```

### Ciclo de vida da chave PIX

`pix.key.registered`, `pix.key.deleted`, `pix.key.blocked` e `pix.key.unblocked` acompanham as chaves da conta e compartilham o mesmo formato; o `pix.key.blocked` inclui também o campo `reason`.

```json
{
  "accountId": 10024270,
  "keyType": "CPF",
  "keyValue": "12345678901",
  "occurredAt": "2026-07-14T22:00:00Z"
}
```

### Reivindicação de portabilidade

`pix.claim.created` avisa a criação de uma reivindicação de portabilidade de chave. `pix.claim.acknowledged`, `pix.claim.confirmed`, `pix.claim.cancelled` e `pix.claim.completed` têm o mesmo formato e acompanham as etapas seguintes.

```json
{
  "accountId": 10024270,
  "claimId": "CLAIM-...",
  "claimType": "PORTABILITY",
  "keyType": "CPF",
  "keyValue": "12345678901",
  "donorIspb": "46026562",
  "claimerIspb": "60746948",
  "donorDeadline": "2026-07-21T22:00:00Z",
  "claimerDeadline": "2026-08-13T22:00:00Z",
  "occurredAt": "2026-07-14T22:00:00Z"
}
```

### TED

`ted.confirmed` e `ted.failed` acompanham o desfecho de uma TED enviada.

```json
{
  "accountId": 1042,
  "transactionId": "TED20260711d8a3695bc594e2577c23",
  "type": "ted",
  "amount": 25000,
  "status": "settled",
  "errorReason": null
}
```

O `status` é `settled` no `ted.confirmed`, e `rejected` ou `timeout` no `ted.failed`, com o motivo em `errorReason`.

### TED recebida

`ted.received` avisa que uma TED de outra instituição foi creditada na conta.

```json
{
  "accountId": 10024270,
  "amount": 150075,
  "numCtrlStr": "STR20260714000000123",
  "messageType": "STR0008",
  "senderIspb": "60746948",
  "senderName": "JOAO PEREIRA",
  "senderDocument": "12345678901",
  "senderAgency": "0001",
  "senderAccount": "445566",
  "receivedAt": "2026-07-14T21:00:00Z"
}
```

### Devolução de TED recebida

`ted.refund.requested` confirma o aceite da devolução de uma TED recebida que você comandou; `ted.refund.completed` avisa a liquidação da devolução no SPB e `ted.refund.failed` avisa que a devolução não foi concluída e o valor reservado voltou a ficar disponível na conta. Correlacione pelo `numCtrlStr` do crédito original.

```json
{
  "accountId": 10024270,
  "amount": 150075,
  "numCtrlStr": "STR20260714000000123",
  "reasonCode": "70",
  "status": "processing"
}
```

No `ted.refund.completed` a entrega traz também `spbNumCtrl`, o número de controle da mensagem de devolução (STR0010), e não repete o campo `status`. No `ted.refund.failed` vêm `errorCode` e `errorMessage` com o motivo reportado pelo SPB.

### Transferência interna

`transfer.confirmed` e `transfer.failed` acompanham a transferência entre duas contas do parceiro.

```json
{
  "accountId": 1042,
  "destinationAccountId": 1043,
  "transactionId": "TEF20260711aabbccddeeff",
  "type": "internal",
  "amount": 5000,
  "status": "settled"
}
```

### Transferência interna recebida

`transfer.received` é a perna de crédito da transferência interna, entregue para a conta que recebeu. `sourceAccountId` identifica a conta de origem.

```json
{
  "accountId": 10024271,
  "sourceAccountId": 10024270,
  "transactionId": "TEF20260714aabbccddeeff",
  "type": "internal",
  "amount": 5000,
  "status": "settled"
}
```

### Infração (MED)

`pix.infraction.created` avisa a abertura de uma infração ou MED sobre uma conta, e `pix.infraction.resolved` traz o resultado da análise.

```json
{
  "accountId": 1042,
  "infractionId": "b17c9a02-4a2f-4d5e-9b1a-77e0a1c2d3e4",
  "blockId": "3f21aa8c-1b2c-4d5e-8f90-0a1b2c3d4e5f",
  "endToEndId": "E4602656220260711100000abcdef123",
  "amount": 25000,
  "status": "created",
  "fraudCategory": "SCAM"
}
```

O `status` é `created` na abertura, e `founded` ou `unfounded` na resolução.

### Defesa da infração

`pix.infraction.defense_submitted` confirma o registro da defesa enviada pelo cliente para uma infração com bloqueio cautelar. `blockId` identifica o bloqueio; o desfecho da análise chega por `pix.infraction.resolved`.

```json
{
  "accountId": 1042,
  "infractionId": "b17c9a02-4a2f-4d5e-9b1a-77e0a1c2d3e4",
  "blockId": "3f21aa8c-1b2c-4d5e-8f90-0a1b2c3d4e5f",
  "endToEndId": "E4602656220260711100000abcdef123",
  "status": "defense_submitted"
}
```

### Intervenção MED

`pix.med.created` confirma a abertura de uma intervenção MED solicitada pela Partner API. `pix.med.completed` avisa que a recuperação concluiu com a devolução dos fundos, e `pix.med.cancelled` avisa o cancelamento. `endToEndId` carrega a transação raiz da intervenção.

```json
{
  "accountId": 1042,
  "medId": "REC-a1b2c3d4",
  "endToEndId": "E4602656220260711100000abcdef123",
  "amount": 25000,
  "status": "REFUND_COMPLETED"
}
```

O `status` é `CREATED` na abertura, `REFUND_COMPLETED` ou `COMPLETED` na conclusão e `CANCELLED` no cancelamento. O campo `amount` (centavos) vem nos desfechos apurados pelo arranjo; na abertura e no cancelamento comandados pela API a entrega pode vir sem ele.

### Tarifa cobrada

`fee.charged` avisa que uma tarifa foi cobrada de uma conta. `originTransactionId` e `originalAmount` apontam para a transação que originou a cobrança.

```json
{
  "feeTransactionId": "5f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
  "accountId": 10024270,
  "feeType": "ted_out",
  "amount": 500,
  "originTransactionId": "E4602656220260714210000aabbccdd0",
  "originTransactionType": "ted",
  "originalAmount": 150075,
  "status": "posted",
  "chargedAt": "2026-07-14T21:00:00Z"
}
```

### Conta criada

`account.created` avisa a abertura de uma conta. O `status` pode ser `active` ou `blocked`, conforme a verificação cadastral.

```json
{
  "accountId": 10024272,
  "customerId": 4711,
  "accountNumber": "100123-4",
  "agency": "0001",
  "iban": "BR9146026562000010010012345P1",
  "accountType": "payment",
  "status": "active"
}
```

### Bloqueio, desbloqueio e encerramento de conta

`account.blocked`, `account.unblocked` e `account.closed` compartilham o mesmo formato. O `status` é `blocked` no bloqueio, `active` no desbloqueio e `closed` no encerramento.

```json
{
  "accountId": 10024272,
  "status": "blocked"
}
```

### Teste

`webhook.test` é a entrega manual, útil para validar a sua URL e a verificação de assinatura. O `POST /webhooks/{id}/test` entrega o evento somente ao webhook informado no caminho, mesmo que ele não assine `webhook.test`; nenhum outro webhook recebe a entrega de teste.

```json
{
  "message": "Test webhook delivery",
  "timestamp": "2026-07-11T10:00:00Z",
  "webhookId": "0f7f279c-217b-4ba2-9a96-27d7401dfe2f"
}
```

## Validar a assinatura

A assinatura é um HMAC-SHA256, em hexadecimal minúsculo, sobre a string `"{timestamp}.{corpo_bruto}"`, usando o segredo do webhook. O header vem como `sha256=<hex>`. Compare o valor calculado com o header, descartando o prefixo `sha256=`. Use o corpo bruto recebido, sem reserializar o JSON.

```javascript
const crypto = require('crypto')

function verificar(corpoBruto, headerAssinatura, timestamp, segredo) {
  const conteudo = `${timestamp}.${corpoBruto}`
  const esperado = crypto.createHmac('sha256', segredo).update(conteudo).digest('hex')
  return `sha256=${esperado}` === headerAssinatura
}
```

Rejeite a entrega se a assinatura não conferir.

## Reentrega automática

Uma entrega sem sucesso é retentada com backoff, em até 8 tentativas: imediato, 30s, 2min, 10min, 30min, 1h, 2h e 4h. Responda `2xx` para confirmar o recebimento. As respostas `400`, `401`, `403`, `404`, `410` e `422` são tratadas como falha permanente, sem retentativa.
