Comece por aqui

Webhooks

Receba as mudanças de estado das transações no seu backend, com retentativa automática.

A MonkeyPay faz um POST na URL que você configurar sempre que uma transação muda para um estado com URL cadastrada.

1Transação muda de estado
2 │
3 ▼
4POST na URL daquele estado
5 ├─ 2xx → entregue
6 └─ erro → nova tentativa
7 espera retryDelay × 2^n,
8 até maxRetries (padrão 3)

1. Configure as URLs

PUT /webhook/resource/events cria ou atualiza a configuração de uma carteira. A configuração nova deve informar walletId; configurações antigas gravadas somente para a empresa continuam sendo usadas como fallback até que a carteira tenha uma configuração própria. Cada estado tem a própria URL; pode ser a mesma para todos.

1{
2 "organizationId": "<sua-organizacao>",
3 "type": "pix",
4 "awaitingPayment": "https://seu-backend.example.com/webhooks/monkeypay",
5 "processing": "https://seu-backend.example.com/webhooks/monkeypay",
6 "completed": "https://seu-backend.example.com/webhooks/monkeypay",
7 "failed": "https://seu-backend.example.com/webhooks/monkeypay",
8 "expired": "https://seu-backend.example.com/webhooks/monkeypay",
9 "refunded": "https://seu-backend.example.com/webhooks/monkeypay",
10 "infraction": "https://seu-backend.example.com/webhooks/monkeypay",
11 "maxRetries": 3,
12 "retryDelay": "30 seconds"
13}

Para configurar uma carteira, informe também walletId:

1{
2 "organizationId": "<sua-organizacao>",
3 "walletId": "<sua-carteira>",
4 "completed": "https://seu-backend.example.com/webhooks/monkeypay"
5}
  • organizationId é o mesmo valor de organizationId nas suas carteiras.
  • Campos omitidos mantêm o valor atual; null desliga o envio daquele estado.
  • Estados disponíveis: created, pending, processing, awaitingPayment, completed, failed, cancelled, expired, refunded, infraction.
  • Não existe URL para partially_refunded: esse estado não gera webhook. Concilie-o pela consulta de transações.

Confira a configuração com GET /webhook/resource/organization-events?organizationId=<sua-organizacao>&walletId=<sua-carteira>. Se a carteira ainda não tiver configuração própria, a resposta poderá representar a configuração legada da empresa usada como fallback. Essa consulta exige monkeypay:webhooks:read, uma permissão separada de monkeypay:pix:write. Um 403 com type terminado em /auth-permission-denied indica falta dessa permissão; com /auth-organization-inactive, indica organização inativa. Confira o type antes de solicitar acesso. Um 404 pode indicar ausência de configuração ou organização fora do escopo da credencial.

2. O que chega no seu endpoint

1POST /webhooks/monkeypay HTTP/1.1
2content-type: application/json
3x-webhook-timestamp: 1790000000
4x-webhook-signature: 5f2b…c9
1{
2 "id": "3f1c9a52-6d0e-4b8e-9d7a-2c4b5e6f7a81",
3 "transactionId": "3f1c9a52-6d0e-4b8e-9d7a-2c4b5e6f7a81",
4 "externalId": "pedido-1234",
5 "type": "pix_in",
6 "status": "completed",
7 "amount": 10.5,
8 "method": "pix",
9 "pixKey": null,
10 "qrCode": "00020126580014br.gov.bcb.pix...",
11 "endToEnd": "E12345678202609241200abcdef012345",
12 "sender": { "name": "Maria da Silva", "document": "12345678909", "bankIspb": "12345678", "bankName": "Banco Pagador", "agency": "0001", "account": "123456" },
13 "receiver": { "name": "Minha Empresa LTDA", "document": "12345678000190", "bankIspb": "87654321", "bankName": "Banco Liquidante", "agency": "0001", "account": "654321" },
14 "errorMessage": null,
15 "createdAt": "2026-09-24T12:00:00.000Z",
16 "updatedAt": "2026-09-24T12:03:10.000Z"
17}

Eventos de infração (MED) vão para a URL infraction e trazem também o objeto infraction (id, type, status, amount, reason, reportedBy, analysisResult, analysisDetails, createdAt, updatedAt). O corpo não informa o nome do evento: identifique a infração pela presença desse objeto. Veja o guia Infrações e MED.

3. Responda rápido e com 2xx

Qualquer resposta 2xx conta como entregue. Resposta diferente de 2xx ou falha de rede agenda nova tentativa, com espera de retryDelay × 2^tentativa (máximo de 15 minutos), até maxRetries (padrão 3). Sem retryDelay, a espera começa em 1 segundo.

Trate a retentativa como ajuda, não como garantia de entrega. Se o seu endpoint ficou fora do ar, confira as tentativas em Histórico de entregas e concilie as transações pela consulta por externalId.

GET /webhook/resource/send-events?organizationId=<sua-organizacao> lista cada envio com a URL de destino, o status (completed, failed ou queued), o HTTP que o seu endpoint respondeu e o total de tentativas. Filtre por externalId ou transactionId para investigar um evento específico.

Grave o evento, responda 200 e processe depois. O mesmo evento pode chegar mais de uma vez: seu processamento precisa aceitar uma repetição sem repetir seus efeitos, como liberar o mesmo pedido duas vezes.

Para eventos de transação, compare também a versão recebida (updatedAt) e os dados já processados. Apenas transactionId + status não identificam toda atualização.

Para MED, acompanhe infraction.id, infraction.updatedAt e infraction.status. Uma infração pode mudar enquanto o status da transação continua igual; descartar o evento só por esse estado perderia a atualização. Se a versão ou a ordem dos eventos for ambígua, consulte a infração pela API antes de atualizar o seu registro. Veja Infrações e MED.

Checklist do seu endpoint: responde 2xx em poucos segundos; grava o evento antes de processar; evita repetir efeitos sem descartar novas versões da transação ou da infração; valida a assinatura quando houver secret; confirma o estado pela API antes de liberar valor.

4. Valide a assinatura

Quando a organização tem secret, cada envio leva:

  • x-webhook-timestamp: segundos desde 1970;
  • x-webhook-signature: HMAC-SHA256 em hexadecimal de <timestamp>.<corpo bruto>, com o secret como chave.
1import { createHmac, timingSafeEqual } from 'node:crypto';
2
3export function isMonkeyPayWebhook(rawBody, headers, secret) {
4 const timestamp = headers['x-webhook-timestamp'];
5 const signature = headers['x-webhook-signature'];
6 // Cabeçalho ausente, repetido ou fora do formato: recuse antes de calcular.
7 if (typeof timestamp !== 'string' || typeof signature !== 'string') return false;
8 if (!/^[0-a-f]{64}$/i.test(signature)) return false;
9
10 // Recuse eventos antigos, ou com timestamp que não é número, para reduzir replay.
11 const sentAt = Number(timestamp);
12 if (!Number.isFinite(sentAt) || Math.abs(Date.now() / 1000 - sentAt) > 300) return false;
13
14 const expected = createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest();
15 return timingSafeEqual(Buffer.from(signature, 'hex'), expected);
16}

Calcule sobre o corpo bruto, exatamente como recebido, antes de qualquer JSON.parse.

O secret (formato whsec_…) volta uma única vez, em data.secret, na resposta de PUT /webhook/resource/events que o gerou: a primeira configuração ou um regenerateSecret: true. Nas demais respostas data é null, e GET /organization-events informa apenas hasSecret. Guarde o valor no seu cofre ao recebê-lo; se perder, gere outro com regenerateSecret: true e atualize a validação. Mesmo com a assinatura válida, confirme o estado com GET /resource/transactions/external-id/{externalId} antes de liberar qualquer valor.

5. Teste antes de operar

POST /webhook/resource/test-send envia para a URL do estado informado um evento igual ao real, assinado quando houver secret. Nenhuma transação é criada.

1{
2 "organizationId": "<sua-organizacao>",
3 "walletId": "<sua-carteira>",
4 "data": {
5 "type": "pix_in",
6 "status": "completed",
7 "payload": {
8 "transactionId": "00000000-0000-4000-8000-000000000000",
9 "externalId": "teste-webhook",
10 "status": "completed"
11 }
12 }
13}

O teste de uma carteira recebe o mesmo walletId usado na configuração.

A API responde 200 quando processa o pedido de teste. Se não houver URL para o estado enviado, nada é entregue — confira no seu endpoint se o evento chegou.