Skip to content

Webhooks

Receba notificações em tempo real sobre eventos da sua conta.

O que são Webhooks?

Webhooks são notificações HTTP POST enviadas automaticamente pela DiviPay quando eventos importantes acontecem, como:

  • Pagamento confirmado
  • Cobrança cancelada
  • Estorno processado
  • Saque realizado

Configurando Webhooks

Conta Principal

Configure o webhook da conta principal:

bash
curl -X PUT https://api.divipay.com.br/api/customer/webhook \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://seusite.com/webhook/divipay"
  }'

Sub-conta

Configure o webhook de uma subconta específica:

bash
curl -X PUT https://api.divipay.com.br/api/customer/{customerId}/webhook \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://seusite.com/webhook/divipay"
  }'

Eventos

charge.paid

Enviado quando um pagamento é confirmado.

json
{
  "event": "charge.paid",
  "chargeId": "cob_abc123",
  "referenceId": "pedido-123",
  "amount": 100.00,
  "paidAt": "2024-11-04T15:30:00.000Z",
  "status": "PAID",
  "customerId": null,
  "paymentMethod": "PIX"
}

charge.canceled

Enviado quando uma cobrança é cancelada.

json
{
  "event": "charge.canceled",
  "chargeId": "cob_abc123",
  "referenceId": "pedido-123",
  "amount": 100.00,
  "canceledAt": "2024-11-04T15:30:00.000Z",
  "status": "CANCELED",
  "reason": "Cancelado pelo cliente"
}

charge.refunded

Enviado quando um estorno é processado.

json
{
  "event": "charge.refunded",
  "chargeId": "cob_abc123",
  "referenceId": "pedido-123",
  "amount": 100.00,
  "refundedAmount": 100.00,
  "refundedAt": "2024-11-04T15:30:00.000Z",
  "status": "REFUNDED"
}

withdraw.completed

Enviado quando um saque é concluído.

json
{
  "event": "withdraw.completed",
  "withdrawId": "wtd_xyz789",
  "customerId": "sub_abc123",
  "amount": 500.00,
  "completedAt": "2024-11-04T15:30:00.000Z",
  "status": "FINISHED"
}

Implementando o Endpoint

Node.js / Express

javascript
const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

app.post('/webhook/divipay', async (req, res) => {
  try {
    const event = req.body;
    
    // Validar assinatura (se configurada)
    // const signature = req.headers['x-divipay-signature'];
    // if (!validateSignature(signature, req.body)) {
    //   return res.status(401).send('Invalid signature');
    // }
    
    console.log('Evento recebido:', event.event);
    
    switch (event.event) {
      case 'charge.paid':
        await handlePaymentConfirmed(event);
        break;
        
      case 'charge.canceled':
        await handlePaymentCanceled(event);
        break;
        
      case 'charge.refunded':
        await handlePaymentRefunded(event);
        break;
        
      case 'withdraw.completed':
        await handleWithdrawCompleted(event);
        break;
        
      default:
        console.log('Evento desconhecido:', event.event);
    }
    
    // IMPORTANTE: Responder rapidamente
    res.status(200).send('OK');
    
  } catch (error) {
    console.error('Erro ao processar webhook:', error);
    res.status(500).send('Error');
  }
});

async function handlePaymentConfirmed(event) {
  // Atualizar pedido no banco de dados
  await db.orders.update({
    where: { id: event.referenceId },
    data: {
      status: 'PAID',
      paidAt: event.paidAt,
      chargeId: event.chargeId
    }
  });
  
  // Enviar email de confirmação
  await sendConfirmationEmail(event.referenceId);
  
  // Processar pedido
  await processOrder(event.referenceId);
}

async function handlePaymentCanceled(event) {
  await db.orders.update({
    where: { id: event.referenceId },
    data: { status: 'CANCELED' }
  });
}

async function handlePaymentRefunded(event) {
  await db.orders.update({
    where: { id: event.referenceId },
    data: { 
      status: 'REFUNDED',
      refundedAt: event.refundedAt
    }
  });
}

async function handleWithdrawCompleted(event) {
  await db.withdraws.update({
    where: { id: event.withdrawId },
    data: { 
      status: 'COMPLETED',
      completedAt: event.completedAt
    }
  });
}

app.listen(3000);

PHP

php
<?php
// webhook.php

// Ler o corpo da requisição
$payload = file_get_contents('php://input');
$event = json_decode($payload, true);

// Log do evento
error_log('Webhook recebido: ' . $event['event']);

// Processar evento
switch ($event['event']) {
    case 'charge.paid':
        handlePaymentConfirmed($event);
        break;
        
    case 'charge.canceled':
        handlePaymentCanceled($event);
        break;
        
    case 'charge.refunded':
        handlePaymentRefunded($event);
        break;
        
    case 'withdraw.completed':
        handleWithdrawCompleted($event);
        break;
        
    default:
        error_log('Evento desconhecido: ' . $event['event']);
}

// Responder rapidamente
http_response_code(200);
echo 'OK';

function handlePaymentConfirmed($event) {
    global $db;
    
    $db->query(
        "UPDATE orders SET status = 'PAID', paid_at = ? WHERE id = ?",
        [$event['paidAt'], $event['referenceId']]
    );
    
    // Processar pedido
    processOrder($event['referenceId']);
}

function handlePaymentCanceled($event) {
    global $db;
    
    $db->query(
        "UPDATE orders SET status = 'CANCELED' WHERE id = ?",
        [$event['referenceId']]
    );
}

function handlePaymentRefunded($event) {
    global $db;
    
    $db->query(
        "UPDATE orders SET status = 'REFUNDED', refunded_at = ? WHERE id = ?",
        [$event['refundedAt'], $event['referenceId']]
    );
}

function handleWithdrawCompleted($event) {
    global $db;
    
    $db->query(
        "UPDATE withdraws SET status = 'COMPLETED', completed_at = ? WHERE id = ?",
        [$event['completedAt'], $event['withdrawId']]
    );
}
?>

Boas Práticas

1. Responda Rapidamente

Sempre responda com status 200 em até 5 segundos. Processe tarefas pesadas de forma assíncrona:

javascript
app.post('/webhook/divipay', async (req, res) => {
  // Responder imediatamente
  res.status(200).send('OK');
  
  // Processar em background
  processWebhookAsync(req.body).catch(console.error);
});

async function processWebhookAsync(event) {
  // Processamento pesado aqui
  await updateDatabase(event);
  await sendEmails(event);
  await updateInventory(event);
}

2. Idempotência

Webhooks podem ser enviados mais de uma vez. Use o chargeId ou withdrawId para evitar processamento duplicado:

javascript
async function handlePaymentConfirmed(event) {
  // Verificar se já foi processado
  const existing = await db.payments.findUnique({
    where: { chargeId: event.chargeId }
  });
  
  if (existing) {
    console.log('Webhook já processado:', event.chargeId);
    return;
  }
  
  // Processar...
  await db.payments.create({
    data: {
      chargeId: event.chargeId,
      referenceId: event.referenceId,
      amount: event.amount,
      status: 'PAID'
    }
  });
}

3. Validação de Assinatura

Valide a assinatura do webhook para garantir autenticidade:

javascript
function validateSignature(signature, payload) {
  const secret = process.env.DIVIPAY_WEBHOOK_SECRET;
  const hash = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
    
  return signature === hash;
}

app.post('/webhook/divipay', (req, res) => {
  const signature = req.headers['x-divipay-signature'];
  
  if (!validateSignature(signature, req.body)) {
    return res.status(401).send('Invalid signature');
  }
  
  // Processar webhook...
});

4. Retry Logic

Implemente retry para webhooks que falharem:

javascript
async function processWebhookWithRetry(event, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      await processWebhook(event);
      return; // Sucesso
    } catch (error) {
      console.error(`Tentativa ${i + 1} falhou:`, error);
      
      if (i === maxRetries - 1) {
        // Última tentativa falhou, enviar alerta
        await sendAlert('Webhook failed after retries', event);
        throw error;
      }
      
      // Aguardar antes de tentar novamente (exponential backoff)
      await sleep(Math.pow(2, i) * 1000);
    }
  }
}

5. Logging

Mantenha logs detalhados para debugging:

javascript
app.post('/webhook/divipay', async (req, res) => {
  const webhookId = generateId();
  
  console.log({
    webhookId,
    event: req.body.event,
    chargeId: req.body.chargeId,
    timestamp: new Date().toISOString()
  });
  
  try {
    await processWebhook(req.body);
    
    console.log({
      webhookId,
      status: 'success'
    });
  } catch (error) {
    console.error({
      webhookId,
      status: 'error',
      error: error.message
    });
  }
  
  res.status(200).send('OK');
});

Testando Webhooks

Localmente com ngrok

bash
# Instalar ngrok
npm install -g ngrok

# Expor porta local
ngrok http 3000

# Usar URL gerada para configurar webhook
# https://abc123.ngrok.io/webhook/divipay

Simulando Eventos

bash
curl -X POST http://localhost:3000/webhook/divipay \
  -H "Content-Type: application/json" \
  -d '{
    "event": "charge.paid",
    "chargeId": "cob_test123",
    "referenceId": "pedido-test",
    "amount": 100.00,
    "paidAt": "2024-11-04T15:30:00.000Z",
    "status": "PAID"
  }'

Troubleshooting

Webhook não está sendo recebido

  1. Verifique se a URL está acessível publicamente
  2. Confirme que está usando HTTPS em produção
  3. Verifique logs do servidor
  4. Teste com ngrok localmente

Webhook recebido mas não processado

  1. Verifique logs de erro
  2. Confirme que está respondendo com status 200
  3. Valide o formato do payload
  4. Teste idempotência

Webhooks duplicados

  1. Implemente verificação de idempotência
  2. Use chargeId como chave única
  3. Armazene webhooks processados

Próximos Passos

Documentação da API DiviPay