Os webhooks notificam a ITP sobre mudanças de status em consentimentos e pagamentos Pix de forma assíncrona e em tempo real, eliminando a necessidade de polling contínuo.
A detentora de conta (ASPSP) dispara as notificações para a URL registrada na ITP assim que ocorre uma mudança de estado. A ITP deve expor um endpoint HTTPS acessível publicamente para receber esses eventos.
Configuração
Requisitos do Endpoint Receptor
Requisito
Descrição
Protocolo
HTTPS obrigatório (certificado válido)
Método aceito
POST
Tempo de resposta
≤ 5 segundos (responda 200 imediatamente)
Idempotência
O mesmo evento pode ser entregue mais de uma vez
Disponibilidade
99,5% ou superior — falhas causam retries
SLA de Notificação
De acordo com as regras do Open Finance Brasil:
Evento
SLA máximo
Mudança de status de pagamento
≤ 1,5 segundo após o evento
Mudança de status de consentimento
≤ 1,5 segundo após o evento
Em caso de falha no recebimento (timeout, 5xx), a detentora realiza retries automáticos.
Pagamento executado com sucesso — consentimento encerrado
REJECTED
Usuário rejeitou ou timeout na jornada de autorização
EXPIRED
Consentimento expirou sem utilização
Implementação do Endpoint Receptor
// Node.js / Express
app.post('/webhooks/itp-pix', async (req, res) => {
// 1. Responder 200 imediatamente
res.status(200).json({ received: true });
const { event, data } = req.body;
// 2. Processar de forma assíncrona
try {
switch (event) {
case 'PAYMENT_STATUS_UPDATED':
if (data.currentStatus === 'ACSC') {
await confirmPaymentSuccess(data.paymentInitiationId, data.endToEndId);
} else if (data.currentStatus === 'RJCT') {
await handlePaymentRejection(data.paymentInitiationId, data.rejectionReason);
}
break;
case 'CONSENT_STATUS_UPDATED':
await updateConsentStatus(data.paymentInitiationId, data.currentStatus);
break;
case 'CONSENT_REJECTED':
await notifyUserConsentDenied(data.paymentInitiationId);
break;
}
} catch (error) {
// Log interno — não retornar erro HTTP (webhook já foi confirmado)
console.error('Erro ao processar webhook:', error);
}
});
Crítico: Sempre responda HTTP 200antes de executar qualquer lógica de negócio. O processamento deve ser assíncrono (fila, worker) para garantir resposta dentro do timeout.
Segurança
Validação de Assinatura
Os webhooks incluem assinatura no header para garantir autenticidade:
x-celcoin-signature: sha256=abc123def456...
Valide antes de processar:
const crypto = require('crypto');
function isValidWebhookSignature(rawBody, signature, secret) {
const expected = 'sha256=' +
crypto.createHmac('sha256', secret)
.update(rawBody) // usar o body bruto (string), não o objeto
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// No middleware Express:
app.post('/webhooks/itp-pix', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-celcoin-signature'];
if (!isValidWebhookSignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Assinatura inválida' });
}
// ... processar
});
Headers do Webhook
Header
Descrição
Content-Type
application/json
x-celcoin-signature
Assinatura HMAC-SHA256 do payload
x-celcoin-event-id
ID único do evento (para deduplicação)
x-celcoin-timestamp
Timestamp do disparo
Idempotência e Deduplicação
Use x-celcoin-event-id para evitar processar o mesmo evento duas vezes:
const redis = require('redis');
const client = redis.createClient();
async function isEventAlreadyProcessed(eventId) {
const result = await client.set(`event:${eventId}`, '1', {
NX: true, // somente se não existir
EX: 86400 // expirar em 24h
});
return result === null; // null = já existia
}
app.post('/webhooks/itp-pix', async (req, res) => {
res.status(200).json({ received: true });
const eventId = req.headers['x-celcoin-event-id'];
if (await isEventAlreadyProcessed(eventId)) return;
processEvent(req.body);
});
Pontos de Atenção
⚠️
Responda 200 imediatamente: O timeout do webhook é curto. Qualquer lógica de negócio deve ser processada de forma assíncrona. Respostas lentas causam retries desnecessários.
⚠️
Não confie apenas em webhooks: Webhooks podem falhar ou sofrer atrasos. Para pagamentos críticos, combine webhooks com polling via GET /payment-initiation/:id como fallback.
⚠️
HTTPS obrigatório: Endpoints HTTP sem TLS são rejeitados. Use certificado válido (não auto-assinado) em produção.
⚠️
Valide a assinatura: Sempre verifique x-celcoin-signature antes de processar o evento para prevenir spoofing.
⚠️
Use o body bruto para validação de assinatura: A assinatura é calculada sobre o payload raw (string), não sobre o objeto JSON parseado. Configure o middleware para preservar o body original.
⚠️
RJCT com rejectionReason: Ao receber rejeição, verifique o código de motivo para determinar se é retriável. Consulte Possíveis Erros de Pagamento para a lista completa.
⚠️
SLA regulatório: O Open Finance Brasil exige que a detentora notifique em ≤ 1,5 segundo. Se seu sistema não receber a notificação em tempo razoável, implemente polling como contingência.