Logo | API Docs
Entrar

Configuracao de Webhooks

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.

Via Painel

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.

Via API

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"
}

Seguranca

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.

Como Verificar a Assinatura

<?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
Importante: Sempre valide a assinatura antes de processar o webhook. Nunca confie apenas no IP de origem.

Eventos Disponiveis

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)

Payload de Cada Evento

transaction.pending

{
  "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"
  }
}

transaction.paid

{
  "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"
  }
}

transaction.cancelled

{
  "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"
  }
}

transaction.reversed

{
  "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"
  }
}

transaction.expired

{
  "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"
  }
}

Politica de Retentativa

Caso seu servidor nao responda com HTTP 200, o sistema reenviara a notificacao seguindo a politica de retentativas abaixo:

Tentativa Intervalo
1a tentativaImediata
2a tentativa30 segundos
3a tentativa1 minuto
4a tentativa5 minutos
5a tentativa15 minutos
6a tentativa1 hora
Responda com HTTP 200 para confirmar o recebimento. Qualquer outro status HTTP sera tratado como falha e acionara uma nova tentativa.
Apos esgotar todas as tentativas, a notificacao sera marcada como falha. Voce pode consultar o historico de webhooks no painel do merchant.

Exemplos de Implementacao

<?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)