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.
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
Evento Quando é 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] .