Webhooks permitem que seu sistema receba notificacoes automaticas sobre mudancas no status das transacoes.
Quando um evento ocorre (ex: pagamento confirmado), enviamos uma requisicao POST para a URL configurada.
Acesse Chaves de Integracao no painel do merchant e configure o campo postback_url
na sua API Key. Todas as transacoes criadas com essa chave enviarao notificacoes para a URL informada.
Envie o parametro postback_url em cada requisicao de criacao de transacao.
A URL informada na requisicao tem prioridade sobre a URL configurada na chave de integracao.
{
"amount": 150.00,
"postback_url": "https://seusite.com/webhook/pagamentos"
}
Todas as notificacoes de webhook incluem uma assinatura HMAC-SHA256 no header
X-Webhook-Signature. Utilize essa assinatura para verificar a autenticidade da requisicao.
O secret utilizado para gerar a assinatura e o seu client_secret, disponivel na secao
API Keys do painel do merchant. Use o mesmo secret que voce utiliza para autenticacao OAuth.
<?php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$secret = 'SEU_CLIENT_SECRET'; // Mesmo client_secret da API Key
$expectedSignature = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expectedSignature, $signature)) {
http_response_code(401);
exit('Assinatura invalida');
}
// Assinatura valida — processar o webhook
$data = json_decode($payload, true);
// ... sua logica aqui
| Evento | Descricao |
|---|---|
transaction.pending |
Transacao criada, aguardando pagamento |
transaction.paid |
Pagamento confirmado com sucesso |
transaction.cancelled |
Transacao cancelada (pelo usuario ou sistema) |
transaction.reversed |
Pagamento estornado |
transaction.expired |
Transacao expirada (tempo limite atingido) |
{
"event": "transaction.pending",
"transaction": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"external_id": "pedido-123",
"type": "deposit",
"method": "pix",
"amount": 150.00,
"tax": 2.25,
"net_amount": 147.75,
"status": "pending",
"pix_copy_paste": "00020126580014br.gov.bcb.pix...",
"pix_qr_code_base64": "data:image/png;base64,...",
"created_at": "2025-01-15T10:30:00Z",
"expires_at": "2025-01-15T11:00:00Z"
}
}
{
"event": "transaction.paid",
"transaction": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"external_id": "pedido-123",
"type": "deposit",
"method": "pix",
"amount": 150.00,
"tax": 2.25,
"net_amount": 147.75,
"status": "paid",
"paid_at": "2025-01-15T10:32:15Z",
"payer_name": "Joao Silva",
"payer_document": "123.456.789-00",
"created_at": "2025-01-15T10:30:00Z"
}
}
{
"event": "transaction.cancelled",
"transaction": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"external_id": "pedido-123",
"type": "deposit",
"method": "pix",
"amount": 150.00,
"status": "cancelled",
"cancelled_at": "2025-01-15T11:00:00Z",
"cancellation_reason": "Cancelado pelo usuario",
"created_at": "2025-01-15T10:30:00Z"
}
}
{
"event": "transaction.reversed",
"transaction": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"external_id": "pedido-123",
"type": "deposit",
"method": "pix",
"amount": 150.00,
"tax": 2.25,
"net_amount": 147.75,
"status": "reversed",
"reversed_at": "2025-01-16T14:20:00Z",
"reversal_reason": "Solicitacao do pagador",
"created_at": "2025-01-15T10:30:00Z",
"paid_at": "2025-01-15T10:32:15Z"
}
}
{
"event": "transaction.expired",
"transaction": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"external_id": "pedido-123",
"type": "deposit",
"method": "boleto",
"amount": 250.00,
"status": "expired",
"expired_at": "2025-01-18T23:59:59Z",
"created_at": "2025-01-15T10:30:00Z"
}
}
Caso seu servidor nao responda com HTTP 200, o sistema reenviara a notificacao seguindo a politica de retentativas abaixo:
| Tentativa | Intervalo |
|---|---|
| 1a tentativa | Imediata |
| 2a tentativa | 30 segundos |
| 3a tentativa | 1 minuto |
| 4a tentativa | 5 minutos |
| 5a tentativa | 15 minutos |
| 6a tentativa | 1 hora |
<?php
// webhook.php
// 1. Ler o payload
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$secret = 'SEU_CLIENT_SECRET'; // Mesmo client_secret da API Key
// 2. Verificar assinatura
$expectedSignature = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expectedSignature, $signature)) {
http_response_code(401);
exit('Assinatura invalida');
}
// 3. Decodificar o payload
$data = json_decode($payload, true);
$event = $data['event'];
$transaction = $data['transaction'];
// 4. Processar o evento
switch ($event) {
case 'transaction.paid':
// Pagamento confirmado
// Atualizar status do pedido no seu sistema
atualizarPedido($transaction['external_id'], 'pago');
// Liberar acesso ao produto/servico
liberarAcesso($transaction['external_id']);
break;
case 'transaction.cancelled':
// Transacao cancelada
atualizarPedido($transaction['external_id'], 'cancelado');
break;
case 'transaction.reversed':
// Pagamento estornado
atualizarPedido($transaction['external_id'], 'estornado');
// Revogar acesso ao produto/servico
revogarAcesso($transaction['external_id']);
break;
case 'transaction.expired':
// Transacao expirada
atualizarPedido($transaction['external_id'], 'expirado');
break;
}
// 5. Responder com 200 para confirmar recebimento
http_response_code(200);
echo json_encode(['received' => true]);
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } }));
const WEBHOOK_SECRET = 'SEU_CLIENT_SECRET'; // Mesmo client_secret da API Key
// Middleware para verificar assinatura
function verificarAssinatura(req, res, next) {
const signature = req.headers['x-webhook-signature'] || '';
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.rawBody)
.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
)) {
return res.status(401).json({ error: 'Assinatura invalida' });
}
next();
}
app.post('/webhook/pagamentos', verificarAssinatura, async (req, res) => {
const { event, transaction } = req.body;
switch (event) {
case 'transaction.paid':
console.log(`Pagamento confirmado: ${transaction.id}`);
// Atualizar pedido no banco de dados
await atualizarPedido(transaction.external_id, 'pago');
break;
case 'transaction.cancelled':
console.log(`Transacao cancelada: ${transaction.id}`);
await atualizarPedido(transaction.external_id, 'cancelado');
break;
case 'transaction.reversed':
console.log(`Pagamento estornado: ${transaction.id}`);
await atualizarPedido(transaction.external_id, 'estornado');
break;
case 'transaction.expired':
console.log(`Transacao expirada: ${transaction.id}`);
await atualizarPedido(transaction.external_id, 'expirado');
break;
default:
console.log(`Evento desconhecido: ${event}`);
}
// Confirmar recebimento
res.status(200).json({ received: true });
});
app.listen(3000, () => console.log('Webhook server rodando na porta 3000'));
import hmac
import hashlib
import json
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = 'SEU_CLIENT_SECRET' # Mesmo client_secret da API Key
def verificar_assinatura(payload: bytes, signature: str) -> bool:
"""Verifica a assinatura HMAC-SHA256 do webhook."""
expected = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route('/webhook/pagamentos', methods=['POST'])
def webhook():
# 1. Verificar assinatura
payload = request.get_data()
signature = request.headers.get('X-Webhook-Signature', '')
if not verificar_assinatura(payload, signature):
return jsonify({'error': 'Assinatura invalida'}), 401
# 2. Decodificar payload
data = json.loads(payload)
event = data['event']
transaction = data['transaction']
# 3. Processar evento
if event == 'transaction.paid':
print(f"Pagamento confirmado: {transaction['id']}")
atualizar_pedido(transaction['external_id'], 'pago')
elif event == 'transaction.cancelled':
print(f"Transacao cancelada: {transaction['id']}")
atualizar_pedido(transaction['external_id'], 'cancelado')
elif event == 'transaction.reversed':
print(f"Pagamento estornado: {transaction['id']}")
atualizar_pedido(transaction['external_id'], 'estornado')
elif event == 'transaction.expired':
print(f"Transacao expirada: {transaction['id']}")
atualizar_pedido(transaction['external_id'], 'expirado')
# 4. Confirmar recebimento
return jsonify({'received': True}), 200
if __name__ == '__main__':
app.run(port=3000, debug=True)