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
- Você cria uma transação (cobrança ou pagamento) com uma URL no campo
webhook - A API PixToPay monitora a transação
- Quando o status muda, a API envia um
POSTpara sua URL - Seu servidor processa a notificação e responde
- 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 OKdentro 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 responseWebhooks 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
| Campo | Tipo | Descrição |
|---|---|---|
paid_at | string | Data e hora do pagamento |
payer | object | Informações do pagador |
payer.name | string | Nome do pagador (da conta bancária) |
payer.document_number | string | CPF/CNPJ do pagador |
first_deposit | boolean | true 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:
| type | method | Descrição |
|---|---|---|
transaction | pix | Cobrança PIX (cash-in) |
withdrawal | payout_pix | Pagamento PIX (cash-out) |
withdrawal | payout_ted | Pagamento 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
-
webhook.site: Teste webhooks sem código
- URL: https://webhook.site (opens in a new tab)
- Gera URL temporária
- Visualiza requisições em tempo real
-
ngrok: Exponha localhost publicamente
ngrok http 3000 -
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
- ✅ Responda rapidamente: Máximo 5 segundos
- ✅ Processe assíncronamente: Use filas/workers
- ✅ Seja idempotente: Trate webhooks duplicados
- ✅ Valide os dados: Nunca confie cegamente
- ✅ Verifique com a API: Confirme status críticos
- ✅ Logue tudo: Mantenha histórico completo
- ✅ Monitore falhas: Configure alertas
- ✅ Use HTTPS: Proteja dados sensíveis
- ✅ Trate erros gracefully: Evite reenvios desnecessários
- ✅ Documente comportamento: Facilite manutenção
Troubleshooting
Webhook não está chegando
- Verifique se a URL está acessível publicamente
- Confirme que o endpoint aceita POST
- Verifique firewall/security groups
- Teste com webhook.site primeiro
Webhooks duplicados
- Implemente idempotência
- Use chave única (id + status)
- Mantenha registro de processados
Timeout
- Responda 200 imediatamente
- Processe em background
- Otimize queries do banco
- Use cache quando possível
Suporte
Se você tiver problemas com webhooks:
- Verifique logs do seu servidor
- Confirme que a transação existe na API
- Entre em contato com o suporte técnico
- Forneça: transaction_id, timestamp, logs de erro