Os webhooks são notificações HTTP automáticas enviadas pela API de Saques sempre que há mudanças importantes no status de um cashout (saque/transferência PIX). Eles permitem que seu sistema seja notificado em tempo real sobre eventos relacionados aos saques, eliminando a necessidade de polling constante da API.
Configuração
A URL de webhook é configurada no cadastro da sua conta. Todas as notificações de eventos de cashout serão enviadas automaticamente para essa URL.
DicaCaso precise de múltiplas URLs de webhook (até 10), entre em contato com nosso suporte para configurar URLs adicionais com filtro por tipo de evento.
Requisitos do Endpoint
Seu endpoint de webhook deve:
- ✅ Aceitar requisições POST
- ✅ Responder com status HTTP 200 para confirmar recebimento
- ✅ Processar o payload JSON enviado no body
- ✅ Responder em até 10 segundos (timeout da nossa API)
- ✅ Usar HTTPS (recomendado para segurança)
- ✅ Implementar idempotência usando o header
Idempotency-Key
Eventos Disponíveis
📋 Lista de Eventos
| Evento | Descrição |
|---|---|
cashout.pending | Uma nova solicitação de cashout foi criada e está aguardando processamento. O saldo já foi debitado. |
cashout.processing | O cashout está sendo processado pelo banco. O PIX está sendo enviado. |
cashout.completed | A transferência PIX foi realizada com sucesso. Os fundos chegaram ao destinatário. |
cashout.failed | O processamento falhou. O saldo foi devolvido automaticamente à sua conta. |
cashout.rejected | A transferência foi rejeitada pelo banco (chave inválida, conta encerrada, etc.). O saldo foi devolvido. |
cashout.cancelled | O cashout foi cancelado pelo sistema. O saldo foi devolvido. |
🔄 Fluxo Típico de Eventos
O fluxo mais comum para um cashout bem-sucedido é:
pending → processing → completed
Fluxos alternativos (com devolução de saldo):
pending → rejected (chave PIX inválida ou conta encerrada)
pending → processing → failed (falha durante o envio do PIX)
pending → cancelled (cancelado pelo sistema)
ImportanteNos status
failed,rejectedecancelled, o valor total (incluindo taxas) é devolvido automaticamente ao saldo da sua conta. Não é necessária nenhuma ação manual.
Status de Cashout
🚦 Todos os Status Possíveis
| Status | Descrição | Final? | Impacto no Saldo |
|---|---|---|---|
pending | Cashout criado, aguardando processamento | Não | -Débito (valor + taxa, já debitado na criação) |
processing | PIX sendo enviado pelo banco | Não | Nenhuma alteração |
completed | Transferência PIX realizada com sucesso | Sim | Nenhuma alteração (débito já ocorreu) |
failed | Falha no processamento | Sim | +Estorno (valor + taxa devolvidos) |
rejected | Rejeitado pelo banco | Sim | +Estorno (valor + taxa devolvidos) |
cancelled | Cancelado pelo sistema | Sim | +Estorno (valor + taxa devolvidos) |
🔀 Transições de Status
┌───────────────┐
┌────►│ completed │ (final - sucesso)
│ └───────────────┘
┌───────────┐ ┌─────────────┤
│ pending ├──►│ processing │
└─────┬─────┘ └─────────────┤
│ │ ┌───────────────┐
│ └────►│ failed │ (final - saldo devolvido)
│ └───────────────┘
│
├──────────────────────────► ┌───────────────┐
│ │ rejected │ (final - saldo devolvido)
│ └───────────────┘
│
└──────────────────────────► ┌───────────────┐
│ cancelled │ (final - saldo devolvido)
└───────────────┘
Nota sobre o saldoO saldo é debitado no momento da criação do cashout (
POST /cashout). Se o cashout falhar em qualquer etapa posterior, o estorno é feito automaticamente e você receberá o webhook correspondente (failed,rejectedoucancelled).
Formato do Payload
Estrutura Base
Todos os eventos seguem a mesma estrutura base. O campo data contém os dados da transação no momento do evento.
{
"id": "evt_f6e5d4c3b2a1f6e5d4c3b2a1",
"event": "cashout.{status}",
"type": "cashout",
"created_at": "2026-02-15T08:00:05.000Z",
"data": {
// Dados da transação (veja abaixo)
}
}| Campo | Tipo | Descrição |
|---|---|---|
id | String | Identificador único do evento. Prefixo evt_. Use para idempotência. |
event | String | Nome do evento no formato cashout.{status}. |
type | String | Tipo do recurso. Sempre "cashout" para saques/transferências. |
created_at | String | Timestamp ISO 8601 de quando o evento foi criado. |
data | Object | Dados da transação no momento do evento (detalhado abaixo). |
Objeto data (Transação Cashout)
data (Transação Cashout)| Campo | Tipo | Descrição |
|---|---|---|
data.id | String (UUID) | ID único da transação no nosso sistema. |
data.status | String | Status atual da transação (ex: completed, failed). |
data.amount | Integer | Valor do saque em centavos (ex: 5000 = R$ 50,00). |
data.currency | String | Código da moeda. Sempre "BRL". |
data.fee | Object | null | Taxas aplicadas. Contém total_fee e net_amount (em centavos). |
data.fee.total_fee | Integer | Taxa total cobrada em centavos. |
data.fee.net_amount | Integer | Valor líquido transferido em centavos (amount - total_fee). |
data.paid_at | String | null | Timestamp ISO 8601 de quando o PIX foi enviado. null se não completou. |
data.created_at | String | Timestamp ISO 8601 da criação do cashout. |
data.pix | Object | Dados da chave PIX de destino. |
data.pix.key_type | String | null | Tipo da chave PIX (CPF, CNPJ, EMAIL, PHONE, EVP). |
data.pix.key | String | null | Valor da chave PIX de destino. |
data.pix.end_to_end_id | String | null | Identificador end-to-end do PIX no Banco Central. Disponível após envio. |
data.external_ref | String | null | Referência externa (se informada na criação). |
data.metadata | Object | Dados extras informados na criação (chave-valor). |
Diferença em relação ao PIX INO webhook de Cashout não inclui o campo
data.customer, pois o destinatário é identificado pela chave PIX (data.pix). Em vez deqrcode, o objetopixcontémkey_typeekey.
Exemplos de Webhooks
1. cashout.pending
cashout.pendingEnviado quando o cashout é criado e está aguardando processamento.
{
"id": "evt_f6e5d4c3b2a1f6e5d4c3b2a1",
"event": "cashout.pending",
"type": "cashout",
"created_at": "2026-02-15T08:00:00.000Z",
"data": {
"id": "bb5a6726-2fd8-40db-b65e-9b5297213601",
"status": "pending",
"amount": 5000,
"currency": "BRL",
"fee": {
"total_fee": 50,
"net_amount": 4950
},
"paid_at": null,
"created_at": "2026-02-15T08:00:00.000Z",
"pix": {
"key_type": "CPF",
"key": "12345678909",
"end_to_end_id": null
},
"external_ref": null,
"metadata": {
"orderId": "456"
}
}
}2. cashout.processing
cashout.processingEnviado quando o banco inicia o processamento da transferência PIX.
{
"id": "evt_a1b2c3d4e5f6a1b2c3d4e5f6",
"event": "cashout.processing",
"type": "cashout",
"created_at": "2026-02-15T08:00:02.000Z",
"data": {
"id": "bb5a6726-2fd8-40db-b65e-9b5297213601",
"status": "processing",
"amount": 5000,
"currency": "BRL",
"fee": {
"total_fee": 50,
"net_amount": 4950
},
"paid_at": null,
"created_at": "2026-02-15T08:00:00.000Z",
"pix": {
"key_type": "CPF",
"key": "12345678909",
"end_to_end_id": null
},
"external_ref": null,
"metadata": {
"orderId": "456"
}
}
}3. cashout.completed
cashout.completedEnviado quando a transferência PIX é realizada com sucesso. Este é o evento que confirma que o dinheiro chegou ao destinatário.
{
"id": "evt_b2c3d4e5f6a1b2c3d4e5f6a1",
"event": "cashout.completed",
"type": "cashout",
"created_at": "2026-02-15T08:00:05.000Z",
"data": {
"id": "bb5a6726-2fd8-40db-b65e-9b5297213601",
"status": "completed",
"amount": 5000,
"currency": "BRL",
"fee": {
"total_fee": 50,
"net_amount": 4950
},
"paid_at": "2026-02-15T08:00:05.000Z",
"created_at": "2026-02-15T08:00:00.000Z",
"pix": {
"key_type": "CPF",
"key": "12345678909",
"end_to_end_id": "E20018183202602150709s669oDbAGB1"
},
"external_ref": null,
"metadata": {
"orderId": "456"
}
}
}
NotaObserve que o campo
data.pix.end_to_end_idagora contém o identificador gerado pelo Banco Central, confirmando que o PIX foi efetivamente enviado. O campodata.paid_attambém é preenchido com o timestamp do envio.
4. cashout.failed
cashout.failedEnviado quando ocorre uma falha no processamento. O saldo é devolvido automaticamente.
{
"id": "evt_c3d4e5f6a1b2c3d4e5f6a1b2",
"event": "cashout.failed",
"type": "cashout",
"created_at": "2026-02-15T08:00:10.000Z",
"data": {
"id": "cc6b7837-3fe9-41ec-c76f-0c6308324712",
"status": "failed",
"amount": 3000,
"currency": "BRL",
"fee": {
"total_fee": 50,
"net_amount": 2950
},
"paid_at": null,
"created_at": "2026-02-15T08:00:00.000Z",
"pix": {
"key_type": "EMAIL",
"key": "[email protected]",
"end_to_end_id": null
},
"external_ref": null,
"metadata": {
"orderId": "789"
}
}
}
AtençãoQuando o status é
failed, o valor total (R$ 30,00 + R$ 0,50 de taxa = R$ 30,50) é estornado automaticamente ao saldo da sua conta. Nenhuma ação manual é necessária.
5. cashout.rejected
cashout.rejectedEnviado quando a transferência é rejeitada pelo banco (ex: chave PIX inválida, conta encerrada).
{
"id": "evt_d4e5f6a1b2c3d4e5f6a1b2c3",
"event": "cashout.rejected",
"type": "cashout",
"created_at": "2026-02-15T08:01:00.000Z",
"data": {
"id": "dd7c8948-4gf0-52fd-d87g-1d7419435823",
"status": "rejected",
"amount": 1000,
"currency": "BRL",
"fee": {
"total_fee": 50,
"net_amount": 950
},
"paid_at": null,
"created_at": "2026-02-15T08:00:30.000Z",
"pix": {
"key_type": "CPF",
"key": "00000000000",
"end_to_end_id": null
},
"external_ref": null,
"metadata": {}
}
}Headers HTTP Enviados
Cada notificação de webhook inclui os seguintes headers HTTP:
| Header | Valor | Descrição |
|---|---|---|
Content-Type | application/json | Formato do corpo da requisição. |
X-Webhook-Source | voidpayments-api | Identifica a origem da notificação. |
X-Transaction-Id | UUID da transação | ID da transação no nosso sistema. |
X-Event | Ex: cashout.completed | Tipo do evento enviado. |
Idempotency-Key | ID do evento (evt_*) | Chave única para deduplicação. Mesmo valor em retentativas. |
Exemplo de headers:
POST /seu-endpoint/webhook HTTP/1.1
Host: seu-dominio.com
Content-Type: application/json
X-Webhook-Source: voidpayments-api
X-Transaction-Id: bb5a6726-2fd8-40db-b65e-9b5297213601
X-Event: cashout.completed
Idempotency-Key: evt_b2c3d4e5f6a1b2c3d4e5f6a1Política de Retry
Como funciona
Nosso sistema garante entrega pelo menos uma vez (at-least-once). Se a entrega de um webhook falhar, tentaremos enviá-lo novamente automaticamente com backoff exponencial.
O que é considerado falha:
- Resposta HTTP com código
5xx - Timeout (sem resposta em 10 segundos)
- Erro de rede (conexão recusada, DNS não resolvido, etc.)
O que é considerado sucesso:
- Resposta HTTP com código
2xx(200-299)
AtençãoRespostas HTTP
4xx(exceto429) não são retentadas, pois indicam um problema no seu endpoint que precisa ser corrigido.
Sequência de retry
| Tentativa | Intervalo aproximado | Acumulado |
|---|---|---|
| 1ª | Imediata | — |
| 2ª | ~1 minuto | ~1 min |
| 3ª | ~5 minutos | ~6 min |
| 4ª | ~15 minutos | ~21 min |
| 5ª | ~1 hora | ~1h21 |
Após 5 tentativas sem sucesso, o webhook é movido para uma fila de mensagens mortas (Dead Letter Queue) e pode ser consultado via endpoint GET /webhooks?transactionId={id} para auditoria.
Boas Práticas de Integração
1. Trate o estorno automaticamente
Ao receber webhooks de failed, rejected ou cancelled, atualize seu sistema para refletir que o saque não foi concluído. O saldo já foi devolvido do nosso lado.
app.post('/webhook', async (req, res) => {
const idempotencyKey = req.headers['idempotency-key'];
// Verificar se já processou este evento
const alreadyProcessed = await cache.get(`webhook:${idempotencyKey}`);
if (alreadyProcessed) {
return res.status(200).json({ message: 'Already processed' });
}
const { event, data } = req.body;
switch (event) {
case 'cashout.completed':
// Saque concluído — atualizar status do pagamento
await markPayoutAsCompleted(data.id, data.pix.end_to_end_id);
break;
case 'cashout.failed':
case 'cashout.rejected':
case 'cashout.cancelled':
// Saque falhou — saldo já foi devolvido automaticamente
await markPayoutAsFailed(data.id, event);
// Opcionalmente, notificar o operador ou tentar novo saque
await notifyOperator(`Cashout ${data.id} falhou: ${event}`);
break;
case 'cashout.processing':
// Saque em processamento — apenas atualizar status
await updatePayoutStatus(data.id, 'processing');
break;
}
// Marcar como processado (TTL de 24h)
await cache.set(`webhook:${idempotencyKey}`, true, 86400);
return res.status(200).json({ message: 'OK' });
});2. Responda rápido, processe depois
Retorne HTTP 200 o mais rápido possível e processe o evento de forma assíncrona:
app.post('/webhook', async (req, res) => {
// Responder imediatamente
res.status(200).json({ received: true });
// Processar em background
processWebhookAsync(req.body).catch(console.error);
});3. Valide a origem
Verifique o header X-Webhook-Source para confirmar que a notificação veio da nossa API:
if (req.headers['x-webhook-source'] !== 'voidpayments-api') {
return res.status(403).json({ error: 'Invalid source' });
}4. Use a consulta de webhooks para auditoria
Se suspeitar que perdeu alguma notificação, consulte o histórico via API:
curl -X GET "https://api.voidxpay.com/v1/webhooks?transactionId=bb5a6726-2fd8-40db-b65e-9b5297213601" \
-u "seu_client_id:seu_client_secret"Suporte
Em caso de dúvidas sobre a integração de webhooks, entre em contato com nossa equipe:
- 📧 Email: [email protected]
- 📖 API Reference: Consulte os endpoints
GET /webhookspara verificar o histórico de notificações enviadas
