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:
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:
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.
{
"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.
{
"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.
{
"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.
{
"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
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
// 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:
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:
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:
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:
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:
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
# Instalar ngrok
npm install -g ngrok
# Expor porta local
ngrok http 3000
# Usar URL gerada para configurar webhook
# https://abc123.ngrok.io/webhook/divipaySimulando Eventos
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
- Verifique se a URL está acessível publicamente
- Confirme que está usando HTTPS em produção
- Verifique logs do servidor
- Teste com ngrok localmente
Webhook recebido mas não processado
- Verifique logs de erro
- Confirme que está respondendo com status 200
- Valide o formato do payload
- Teste idempotência
Webhooks duplicados
- Implemente verificação de idempotência
- Use
chargeIdcomo chave única - Armazene webhooks processados