Webhooks

Webhooks

Webhooks são notificações automáticas enviadas pela API PixToPay para informar sobre mudanças no ciclo de vida das transações. Quando você fornece uma URL no parâmetro webhook ao criar uma transação, receberá requisições POST nessa URL sempre que o status da transação mudar.

Como Funcionam

  1. Você cria uma transação (cobrança ou pagamento) com uma URL no campo webhook
  2. A API PixToPay monitora a transação
  3. Quando o status muda, a API envia um POST para sua URL
  4. Seu servidor processa a notificação e responde
  5. Você atualiza seu sistema com o novo status

Configuração

Requisitos do Endpoint

Seu endpoint de webhook deve:

  • ✅ Aceitar requisições POST
  • ✅ Responder com status 200 OK dentro de 5 segundos
  • ✅ Estar acessível publicamente (HTTPS recomendado)
  • ✅ Processar requisições de forma idempotente

Exemplo de Endpoint

// Node.js/Express
app.post("/webhooks/pixtopay", (req, res) => {
  const webhook = req.body;
 
  // Processar webhook
  console.log("Webhook recebido:", webhook);
 
  // Responder rapidamente
  res.status(200).send("OK");
 
  // Processar em segundo plano
  processWebhook(webhook);
});
# Python/Flask
@app.route('/webhooks/pixtopay', methods=['POST'])
def webhook_handler():
    webhook = request.json
 
    # Processar webhook
    print('Webhook recebido:', webhook)
 
    # Responder rapidamente
    response = make_response('OK', 200)
 
    # Processar em segundo plano
    process_webhook.delay(webhook)
 
    return response

Webhooks de Cobranças PIX (Cash-in)

PIX - Pago (Status 1)

Enviado quando uma cobrança PIX é paga pelo cliente.

{
  "id": 123456789,
  "transaction_id": "brand_123456789",
  "currency": "BRL",
  "amount": 20,
  "type": "transaction",
  "method": "pix",
  "status": 1,
  "created_at": "2025-12-16T23:54:36.000Z",
  "paid_at": "2025-12-16T23:55:08.000Z",
  "name": "John Cena",
  "document_number": "12345678910",
  "phone_number": "9999999999",
  "email": "johncena@wwe.com",
  "payer": { "name": "John Cena", "document_number": "12345678910" },
  "e2eId": "E18236120202512170254s090902ad25",
  "external_id": "",
  "first_deposit": true
}

Campos Adicionais

CampoTipoDescrição
paid_atstringData e hora do pagamento
payerobjectInformações do pagador
payer.namestringNome do pagador (da conta bancária)
payer.document_numberstringCPF/CNPJ do pagador
first_depositbooleantrue se é o primeiro pagamento deste pagador

PIX - Expirado (Status 3)

Enviado quando uma cobrança PIX expira sem ser paga.

{
  "id": 123456789,
  "transaction_id": "brand_123456789",
  "currency": "BRL",
  "amount": 45,
  "type": "transaction",
  "method": "pix",
  "status": 3,
  "created_at": "2025-12-16T13:50:33.000Z",
  "paid_at": null,
  "name": "John Cena",
  "document_number": "12345678910",
  "phone_number": "9999999999",
  "email": "johncena@wwe.com",
  "payer": { "name": null, "document_number": null },
  "e2eId": null,
  "external_id": "123456789"
}

PIX - Devolvido (Status 4)

Enviado quando uma cobrança PIX paga é devolvida.

{
  "id": 123456789,
  "transaction_id": "brand_123456789",
  "currency": "BRL",
  "amount": 7.61,
  "type": "transaction",
  "method": "pix",
  "status": 4,
  "created_at": "2025-12-16T21:35:49.000Z",
  "paid_at": "2025-12-16T21:36:33.000Z",
  "name": "John Cena",
  "document_number": "12345678910",
  "phone_number": "9999999999",
  "email": "johncena@wwe.com",
  "payer": { "name": "John Cena", "document_number": "12345678910" },
  "e2eId": "E60746948202512170036a5246dhgtda",
  "external_id": "123456789"
}

Webhooks de Pagamentos (Cash-out)

Payout PIX - Aprovado (Status 1)

Enviado quando um pagamento PIX é aprovado e concluído.

{
  "id": 123456789,
  "transaction_id": "brand_123456789",
  "currency": "BRL",
  "amount": 316.32,
  "type": "withdrawal",
  "method": "payout_pix",
  "status": 1,
  "created_at": "2025-12-16T21:36:50.000Z",
  "paid_at": "2025-12-16T21:36:52.000Z",
  "name": "John Cena",
  "document_number": "9999999999",
  "phone_number": null,
  "email": "johncena@wwe.com",
  "manual_withdrawal": 0,
  "external_id": "123456789"
}

Payout PIX - Rejeitado (Status 2)

Enviado quando um pagamento PIX é rejeitado.

{
  "id": 123456789,
  "transaction_id": "brand_123456789",
  "currency": "BRL",
  "amount": 65.24,
  "type": "withdrawal",
  "method": "payout_pix",
  "status": 2,
  "created_at": "2025-12-16T21:39:01.000Z",
  "paid_at": null,
  "name": "John Cena",
  "document_number": "12345678910",
  "phone_number": "9999999999",
  "email": "johncena@wwe.com",
  "manual_withdrawal": 0,
  "external_id": "123456789",
  "cancel_reason": "invalid_pix_key",
  "cancel_details": "Chave pix inválida"
}

Payout PIX - Rejeitado (Status 3)

Enviado quando um pagamento PIX é rejeitado pelo banco.

{
  "id": 123456789,
  "transaction_id": "brand_123456789",
  "currency": "BRL",
  "amount": 25,
  "type": "withdrawal",
  "method": "payout_pix",
  "status": 3,
  "created_at": "2025-12-16T23:25:53.000Z",
  "paid_at": "2025-12-16T23:25:56.000Z",
  "name": "John Cena",
  "document_number": "12345678910",
  "phone_number": null,
  "email": "johncena@wwe.com",
  "manual_withdrawal": 0,
  "external_id": "123456789",
  "cancel_reason": "refunded",
  "cancel_details": "Valor total devolvido"
}

Identificando Tipos de Webhook

Use os campos type e method para identificar o tipo de webhook:

typemethodDescrição
transactionpixCobrança PIX (cash-in)
withdrawalpayout_pixPagamento PIX (cash-out)
withdrawalpayout_tedPagamento TED (cash-out)

Segurança

1. Validação de origem

Verifique o IP de origem da requisição:

const PIXTOPAY_IPS = ["IP_DA_PIXTOPAY"]; // Consulte com o suporte
 
function validateOrigin(req) {
  const clientIp = req.ip;
  return PIXTOPAY_IPS.includes(clientIp);
}

2. Validação de dados

Sempre valide os dados recebidos:

function validateWebhook(webhook) {
  return (
    webhook.id &&
    webhook.transaction_id &&
    webhook.status !== undefined &&
    webhook.type &&
    webhook.method
  );
}

3. Idempotência

Processe cada webhook apenas uma vez:

const processedWebhooks = new Set();
 
function processWebhook(webhook) {
  const webhookKey = `${webhook.id}_${webhook.status}`;
 
  if (processedWebhooks.has(webhookKey)) {
    console.log("Webhook já processado");
    return;
  }
 
  processedWebhooks.add(webhookKey);
 
  // Processar webhook
  updateTransaction(webhook);
}

4. Verificação adicional

Após receber um webhook, consulte a API para confirmar:

async function verifyWebhook(webhook) {
  const response = await fetch(
    `https://api.pixtopay.com.br/v2/transactions?id=${webhook.id}`,
    {
      headers: { Authorization: "YOUR_API_KEY" },
    }
  );
 
  const transaction = await response.json();
  return transaction.status === webhook.status;
}

Tratamento de Erros

Retry automático

Se seu endpoint não responder ou retornar erro, a PixToPay tentará reenviar o webhook:

  • Tentativas: Até 5 tentativas
  • Intervalo: Exponencial (1min, 5min, 15min, 1h, 6h)
  • Timeout: 5 segundos por tentativa

Implementação robusta

app.post("/webhooks/pixtopay", async (req, res) => {
  try {
    const webhook = req.body;
 
    // Validar
    if (!validateWebhook(webhook)) {
      return res.status(400).send("Invalid webhook");
    }
 
    // Responder rapidamente
    res.status(200).send("OK");
 
    // Processar assíncronamente
    await processWebhookAsync(webhook);
  } catch (error) {
    console.error("Erro ao processar webhook:", error);
    // Ainda retorna 200 para evitar reenvios desnecessários
    // se o erro for de lógica de negócio
    res.status(200).send("Error processed");
  }
});
 
async function processWebhookAsync(webhook) {
  try {
    // Verificar se já foi processado
    const exists = await checkIfProcessed(webhook);
    if (exists) return;
 
    // Verificar com a API
    const valid = await verifyWebhook(webhook);
    if (!valid) {
      console.warn("Webhook inválido");
      return;
    }
 
    // Processar
    await updateDatabaseTransacao(webhook);
 
    // Notificar usuário
    await notifyUser(webhook);
 
    // Marcar como processado
    await markAsProcessed(webhook);
  } catch (error) {
    console.error("Erro no processamento assíncrono:", error);
    // Logar para análise posterior
    await logError(webhook, error);
  }
}

Casos de Uso

1. Atualização de status em tempo real

async function updateTransaction(webhook) {
  await database.transactions.update(
    { transaction_id: webhook.transaction_id },
    {
      status: webhook.status,
      paid_at: webhook.paid_at,
      updated_at: new Date(),
    }
  );
}

2. Notificação ao cliente

async function notifyUser(webhook) {
  if (webhook.status === 1 && webhook.type === "transaction") {
    await sendEmail(webhook.email, {
      subject: "Pagamento Confirmado",
      body: `Seu pagamento de R$ ${webhook.amount} foi confirmado!`,
    });
  }
}

3. Liberação de produto/serviço

async function handlePayment(webhook) {
  if (webhook.status === 1 && webhook.type === "transaction") {
    const order = await getOrderByTransactionId(webhook.transaction_id);
 
    // Liberar produto
    await releaseProduct(order.id);
 
    // Enviar nota fiscal
    await sendInvoice(order.id);
 
    // Notificar cliente
    await notifyProductReady(order.user_id);
  }
}

4. Controle de fraude

async function checkFraud(webhook) {
  if (webhook.first_deposit && webhook.status === 1) {
    // Primeiro depósito - verificar
    const fraudScore = await analyzeFraud({
      payer: webhook.payer,
      amount: webhook.amount,
      receiver: webhook.document_number,
    });
 
    if (fraudScore > 0.8) {
      // Alto risco - devolver
      await refundTransaction(webhook.id);
    }
  }
}

Testando Webhooks

Ferramentas úteis

  1. webhook.site: Teste webhooks sem código

  2. ngrok: Exponha localhost publicamente

    ngrok http 3000
  3. Postman: Simule webhooks manualmente

    • Configure mock server
    • Teste sua lógica de processamento

Exemplo de teste local

// test-webhook.js
const axios = require("axios");
 
async function testWebhook() {
  const webhookData = {
    id: 12345,
    transaction_id: "test-123",
    status: 1,
    type: "transaction",
    method: "pix",
    amount: 100,
    currency: "BRL",
  };
 
  try {
    const response = await axios.post(
      "http://localhost:3000/webhooks/pixtopay",
      webhookData
    );
    console.log("Teste bem-sucedido:", response.status);
  } catch (error) {
    console.error("Erro no teste:", error.message);
  }
}
 
testWebhook();

Boas Práticas

  1. Responda rapidamente: Máximo 5 segundos
  2. Processe assíncronamente: Use filas/workers
  3. Seja idempotente: Trate webhooks duplicados
  4. Valide os dados: Nunca confie cegamente
  5. Verifique com a API: Confirme status críticos
  6. Logue tudo: Mantenha histórico completo
  7. Monitore falhas: Configure alertas
  8. Use HTTPS: Proteja dados sensíveis
  9. Trate erros gracefully: Evite reenvios desnecessários
  10. Documente comportamento: Facilite manutenção

Troubleshooting

Webhook não está chegando

  1. Verifique se a URL está acessível publicamente
  2. Confirme que o endpoint aceita POST
  3. Verifique firewall/security groups
  4. Teste com webhook.site primeiro

Webhooks duplicados

  1. Implemente idempotência
  2. Use chave única (id + status)
  3. Mantenha registro de processados

Timeout

  1. Responda 200 imediatamente
  2. Processe em background
  3. Otimize queries do banco
  4. Use cache quando possível

Suporte

Se você tiver problemas com webhooks:

  1. Verifique logs do seu servidor
  2. Confirme que a transação existe na API
  3. Entre em contato com o suporte técnico
  4. Forneça: transaction_id, timestamp, logs de erro