Skip to main content
Webhooks permitem que sua aplicação seja notificada automaticamente quando eventos ocorrem — como um pagamento confirmado ou uma transação expirada. Em vez de ficar consultando o status, a gente avisa você.

Configurando um webhook

Acesse o dashboard → seu projeto → Webhooks → Adicionar webhook. Você vai precisar de:
  • Uma URL HTTPS pública que receberá os eventos
  • Um secret para validar a autenticidade das requisições
Webhooks funcionam nos dois ambientes. No sandbox, os eventos são disparados normalmente ao simular pagamentos — ideal para testar sua integração de ponta a ponta.

Formato do payload

Toda notificação chega como um POST com o seguinte formato:
{
  "event": "transaction_paid",
  "data": {
    "id": "clx7a8b9c0d1e2f3g4h5",
    "status": "paid",
    "amountCents": 10000,
    "paidAt": "2026-01-16T10:05:00.000Z"
  },
  "timestamp": "2026-01-16T10:05:01.000Z"
}
Responda com 200 OK assim que receber. Se demorar mais de 10 segundos ou retornar outro status, tentamos reenviar com backoff exponencial (até 5 tentativas).

Eventos disponíveis

EventoQuando é disparado
transaction_createdTransação PIX criada
transaction_paidPagamento confirmado
transaction_expiredPIX expirou sem pagamento
transaction_cancelledTransação cancelada
transaction_refundedPagamento estornado

transaction_created

{
  "event": "transaction_created",
  "data": {
    "id": "clx7a8b9c0d1e2f3g4h5",
    "externalId": "pedido_123",
    "status": "waiting_payment",
    "amountCents": 10000,
    "product": "Plano Premium",
    "customer": {
      "id": "cly1234567890",
      "name": "João Silva",
      "document": "12345678900",
      "email": "[email protected]"
    },
    "createdAt": "2026-01-16T10:00:00.000Z"
  },
  "timestamp": "2026-01-16T10:00:01.000Z"
}

transaction_paid

{
  "event": "transaction_paid",
  "data": {
    "id": "clx7a8b9c0d1e2f3g4h5",
    "externalId": "pedido_123",
    "status": "paid",
    "amountCents": 10000,
    "paidAt": "2026-01-16T10:05:00.000Z",
    "customer": {
      "id": "cly1234567890",
      "name": "João Silva",
      "document": "12345678900",
      "email": "[email protected]"
    }
  },
  "timestamp": "2026-01-16T10:05:01.000Z"
}

transaction_expired

{
  "event": "transaction_expired",
  "data": {
    "id": "clx7a8b9c0d1e2f3g4h5",
    "externalId": "pedido_123",
    "status": "expired",
    "amountCents": 10000,
    "expirationDate": "2026-01-16T10:30:00.000Z"
  },
  "timestamp": "2026-01-16T10:30:01.000Z"
}

transaction_cancelled

{
  "event": "transaction_cancelled",
  "data": {
    "id": "clx7a8b9c0d1e2f3g4h5",
    "externalId": "pedido_123",
    "status": "cancelled",
    "amountCents": 10000
  },
  "timestamp": "2026-01-16T10:15:00.000Z"
}

transaction_refunded

{
  "event": "transaction_refunded",
  "data": {
    "id": "clx7a8b9c0d1e2f3g4h5",
    "externalId": "pedido_123",
    "status": "refunded",
    "amountCents": 10000,
    "paidAt": "2026-01-16T10:05:00.000Z",
    "refundedAt": "2026-01-17T14:00:00.000Z"
  },
  "timestamp": "2026-01-17T14:00:01.000Z"
}

Verificação de assinatura

Toda requisição inclui o header X-Webhook-Signature com uma assinatura HMAC-SHA256 do body. Valide sempre para garantir que a requisição veio da Bob Payments.
Use comparação timing-safe para evitar ataques de timing. Exemplos abaixo já fazem isso corretamente.
import crypto from 'crypto';

function verifyWebhookSignature(rawBody, signatureHeader, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(rawBody);
  const expected = hmac.digest('hex');

  const expectedBuffer = Buffer.from(expected, 'utf8');
  const receivedBuffer = Buffer.from(signatureHeader, 'utf8');

  if (expectedBuffer.length !== receivedBuffer.length) return false;
  return crypto.timingSafeEqual(expectedBuffer, receivedBuffer);
}

// Com Express
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const isValid = verifyWebhookSignature(
    req.body,
    req.headers['x-webhook-signature'],
    'seu_webhook_secret'
  );

  if (!isValid) return res.status(401).json({ error: 'Invalid signature' });

  const event = JSON.parse(req.body);
  // processar evento...

  res.status(200).json({ received: true });
});
Use o raw body para verificar a assinatura — nunca o JSON já parseado. Re-serializar o JSON pode alterar a formatação e invalidar a assinatura.

Boas práticas e notas importantes

  • O campo isSandbox indica se o evento ocorreu no ambiente de sandbox
  • Valores monetários são expressos em centavos (amountCents)
  • O campo event identifica o tipo de evento recebido
  • Retorne 200 imediatamente e processe o evento em background
  • Implemente processamento idempotente — processe cada id de evento uma única vez
  • Registre falhas de verificação de assinatura e responda com 401 apropriadamente

Precisa de ajuda?

Nossa equipe está disponível para auxiliar no processo de desenvolvimento. Entre em contato pelo e-mail [email protected].