Pular para o conteúdo principal

Webhooks de Saque (Payout)

Eventos relacionados ao ciclo de vida das solicitações de saque para submerchants do Fast Connect.

Eventos Disponíveis

EventoDescrição
payout.approvedEnviado quando uma solicitação de saque é aprovada pelo gateway
payout.rejectedEnviado quando uma solicitação de saque é rejeitada pelo gateway

Pré-requisitos

  • Fast Connect habilitado no merchant master
  • Submerchant com parent_merchant_id configurado
  • Webhook endpoints configurados no submerchant
  • Eventos habilitados nos enabled_events do webhook endpoint (ou lista vazia para habilitar todos)

Payload

Todos os webhooks de saque seguem a estrutura padrão:

{
"id": "evt_2RhQg9M7ZCg3X3nMb9W1kX8Q",
"event": "payout.approved" | "payout.rejected",
"data": {
"id": "2vorkDcXyvzifL63YX09S9VqcnI",
"merchantId": "36OO3bcGjhPjjF3tzrqOGcmQuKo",
"amount": 10000,
"currency": "BRL",
"status": "approved" | "rejected",
"payoutFee": 100,
"netAmount": 9900,
"rejectionReason": "..." // apenas se rejeitado
}
}

payout.approved

Enviado quando uma solicitação de saque é aprovada pelo gateway e as operações no ledger são concluídas com sucesso.

{
"id": "evt_2RhQg9M7ZCg3X3nMb9W1kX8Q",
"event": "payout.approved",
"data": {
"id": "2vorkDcXyvzifL63YX09S9VqcnI",
"merchantId": "36OO3bcGjhPjjF3tzrqOGcmQuKo",
"amount": 10000,
"currency": "BRL",
"status": "approved",
"payoutFee": 100,
"netAmount": 9900,
"externalAccountDetails": {
"type": "pix",
"pixKey": "12345678000199"
},
"processedAt": "2025-12-04T18:45:52.988Z",
"createdAt": "2025-12-04T18:30:00.000Z",
"updatedAt": "2025-12-04T18:45:52.988Z"
}
}

Campos importantes:

  • amount: Valor bruto solicitado (em centavos)
  • payoutFee: Taxa de saque calculada (em centavos)
  • netAmount: Valor líquido que será transferido após dedução das taxas
  • processedAt: Data/hora em que o saque foi aprovado e processado

payout.rejected

Enviado quando uma solicitação de saque é rejeitada pelo gateway.

{
"id": "evt_2RhQg9M7ZCg3X3nMb9W1kX8Q",
"event": "payout.rejected",
"data": {
"id": "2vorkDcXyvzifL63YX09S9VqcnI",
"merchantId": "36OO3bcGjhPjjF3tzrqOGcmQuKo",
"amount": 10000,
"currency": "BRL",
"status": "rejected",
"payoutFee": 100,
"netAmount": 9900,
"rejectionReason": "Documentação incompleta",
"externalAccountDetails": {
"type": "pix",
"pixKey": "12345678000199"
},
"processedAt": "2025-12-04T18:45:52.988Z",
"createdAt": "2025-12-04T18:30:00.000Z",
"updatedAt": "2025-12-04T18:45:52.988Z"
}
}

Campos importantes:

  • rejectionReason: Motivo da rejeição informado pelo gateway
  • processedAt: Data/hora em que o saque foi rejeitado

Processo de Envio

  1. Busca de endpoints:

    • Sistema busca webhook_endpoints do submerchant
    • Filtra por eventos habilitados (enabled_events contém o evento ou está vazio)
  2. Envio:

    • Envia para todos os endpoints configurados
    • Usa Promise.allSettled (não falha se algum endpoint falhar)
    • Registra logs de sucesso/falha
  3. Timing:

    • Webhooks são enviados após o commit da transação
    • Garante que os dados já estão persistidos no banco

Exemplo de Implementação

app.post('/webhooks/fastpay', (req, res) => {
const { id, event, data } = req.body;

switch (event) {
case 'payout.approved':
console.log(`Saque aprovado: ${data.id}`);
console.log(`Valor líquido: R$ ${(data.netAmount / 100).toFixed(2)}`);
console.log(`Taxa: R$ ${(data.payoutFee / 100).toFixed(2)}`);

// Atualizar status na sua base de dados
// Notificar o submerchant
// Registrar no log de transações
break;

case 'payout.rejected':
console.log(`Saque rejeitado: ${data.id}`);
console.log(`Motivo: ${data.rejectionReason}`);

// Atualizar status na sua base de dados
// Notificar o submerchant sobre a rejeição
// Registrar motivo da rejeição
break;

default:
console.log(`Evento desconhecido: ${event}`);
}

res.status(200).send('OK');
});

Fluxo Completo

Valores em Centavos

Todos os valores monetários são enviados em centavos:

  • amount: 10000 = R$ 100,00
  • payoutFee: 100 = R$ 1,00
  • netAmount: 9900 = R$ 99,00

Para converter para reais, divida por 100:

const amountInReais = data.amount / 100;
const feeInReais = data.payoutFee / 100;
const netAmountInReais = data.netAmount / 100;

Idempotência

Webhooks podem ser enviados mais de uma vez. Use o campo id para evitar processamento duplicado:

const processedEvents = new Set();

app.post('/webhooks/fastpay', (req, res) => {
const { id, event, data } = req.body;

// Verificar se já processamos este evento
if (processedEvents.has(id)) {
return res.status(200).send('Already processed');
}

// Processar evento...
processPayoutEvent(event, data);

// Marcar como processado
processedEvents.add(id);
res.status(200).send('OK');
});

Tratamento de Erros

Se seu endpoint retornar um erro (status diferente de 2xx), a FastPay tentará reenviar:

  • Tentativa 1: Imediatamente
  • Tentativa 2: Após 1 minuto
  • Tentativa 3: Após 5 minutos
  • Tentativa 4: Após 15 minutos
  • Tentativa 5: Após 1 hora

Após 5 tentativas falhadas, o webhook é marcado como "failed" no painel.

Configuração de Webhooks

Os webhooks de saque podem ser configurados:

  1. Via Painel da FastPay:

    • Navegue até FastConnect > Gestão de Subcontas > Webhooks
    • Configure os endpoints para o submerchant
  2. Via API:

    • Use o endpoint de configuração de webhooks do submerchant
    • Informe os eventos desejados em enabled_events ou deixe vazio para todos

Notas Importantes

  • Webhooks são enviados apenas para submerchants (com parent_merchant_id)
  • O sistema respeita enabled_events configurados no webhook endpoint
  • Falhas no envio não interrompem o fluxo de aprovação/rejeição
  • Todos os valores monetários são em centavos
  • A moeda é sempre BRL para submerchants
  • O saque é realizado via PIX usando o CNPJ do submerchant como chave