Cashout

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.

📘

Dica

Caso 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

EventoDescrição
cashout.pendingUma nova solicitação de cashout foi criada e está aguardando processamento. O saldo já foi debitado.
cashout.processingO cashout está sendo processado pelo banco. O PIX está sendo enviado.
cashout.completedA transferência PIX foi realizada com sucesso. Os fundos chegaram ao destinatário.
cashout.failedO processamento falhou. O saldo foi devolvido automaticamente à sua conta.
cashout.rejectedA transferência foi rejeitada pelo banco (chave inválida, conta encerrada, etc.). O saldo foi devolvido.
cashout.cancelledO 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)
🚧

Importante

Nos status failed, rejected e cancelled, 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

StatusDescriçãoFinal?Impacto no Saldo
pendingCashout criado, aguardando processamentoNão-Débito (valor + taxa, já debitado na criação)
processingPIX sendo enviado pelo bancoNãoNenhuma alteração
completedTransferência PIX realizada com sucessoSimNenhuma alteração (débito já ocorreu)
failedFalha no processamentoSim+Estorno (valor + taxa devolvidos)
rejectedRejeitado pelo bancoSim+Estorno (valor + taxa devolvidos)
cancelledCancelado pelo sistemaSim+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 saldo

O 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, rejected ou cancelled).


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)
  }
}
CampoTipoDescrição
idStringIdentificador único do evento. Prefixo evt_. Use para idempotência.
eventStringNome do evento no formato cashout.{status}.
typeStringTipo do recurso. Sempre "cashout" para saques/transferências.
created_atStringTimestamp ISO 8601 de quando o evento foi criado.
dataObjectDados da transação no momento do evento (detalhado abaixo).

Objeto data (Transação Cashout)

CampoTipoDescrição
data.idString (UUID)ID único da transação no nosso sistema.
data.statusStringStatus atual da transação (ex: completed, failed).
data.amountIntegerValor do saque em centavos (ex: 5000 = R$ 50,00).
data.currencyStringCódigo da moeda. Sempre "BRL".
data.feeObject | nullTaxas aplicadas. Contém total_fee e net_amount (em centavos).
data.fee.total_feeIntegerTaxa total cobrada em centavos.
data.fee.net_amountIntegerValor líquido transferido em centavos (amount - total_fee).
data.paid_atString | nullTimestamp ISO 8601 de quando o PIX foi enviado. null se não completou.
data.created_atStringTimestamp ISO 8601 da criação do cashout.
data.pixObjectDados da chave PIX de destino.
data.pix.key_typeString | nullTipo da chave PIX (CPF, CNPJ, EMAIL, PHONE, EVP).
data.pix.keyString | nullValor da chave PIX de destino.
data.pix.end_to_end_idString | nullIdentificador end-to-end do PIX no Banco Central. Disponível após envio.
data.external_refString | nullReferência externa (se informada na criação).
data.metadataObjectDados extras informados na criação (chave-valor).
📘

Diferença em relação ao PIX IN

O webhook de Cashout não inclui o campo data.customer, pois o destinatário é identificado pela chave PIX (data.pix). Em vez de qrcode, o objeto pix contém key_type e key.


Exemplos de Webhooks

1. cashout.pending

Enviado 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

Enviado 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

Enviado 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"
    }
  }
}
📘

Nota

Observe que o campo data.pix.end_to_end_id agora contém o identificador gerado pelo Banco Central, confirmando que o PIX foi efetivamente enviado. O campo data.paid_at também é preenchido com o timestamp do envio.

4. cashout.failed

Enviado 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ção

Quando 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

Enviado 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:

HeaderValorDescrição
Content-Typeapplication/jsonFormato do corpo da requisição.
X-Webhook-Sourcevoidpayments-apiIdentifica a origem da notificação.
X-Transaction-IdUUID da transaçãoID da transação no nosso sistema.
X-EventEx: cashout.completedTipo do evento enviado.
Idempotency-KeyID 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_b2c3d4e5f6a1b2c3d4e5f6a1

Polí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ção

Respostas HTTP 4xx (exceto 429) não são retentadas, pois indicam um problema no seu endpoint que precisa ser corrigido.

Sequência de retry

TentativaIntervalo aproximadoAcumulado
Imediata
~1 minuto~1 min
~5 minutos~6 min
~15 minutos~21 min
~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 /webhooks para verificar o histórico de notificações enviadas