Sweeping Accounts - Webhooks
Webhooks - Transferências Inteligentes (Sweeping Accounts)
Visão Geral
Os webhooks notificam a ITP (instituição iniciadora) sobre mudanças de status em consentimentos e pagamentos de Sweeping Accounts, eliminando a necessidade de polling contínuo.
A detentora de conta (ASPSP) é responsável por disparar as notificações. A ITP deve expor um endpoint HTTPS para receber essas notificações e processar as atualizações em seu sistema.
Configuração do Endpoint de Webhook
A URL de webhook da ITP deve ser:
- Acessível publicamente via HTTPS
- Capaz de responder com
HTTP 200em até 5 segundos - Idempotente — prepared para receber o mesmo evento mais de uma vez
SLA de Notificação
De acordo com as regras do Open Finance Brasil:
| Evento | SLA da primeira tentativa |
|---|---|
| Mudança de status do pagamento | ≤ 1,5 segundo após o evento |
| Mudança de status do consentimento | ≤ 1,5 segundo após o evento |
Em caso de falha no recebimento (timeout, 5xx), a detentora deve tentar novamente conforme política de retry.
Eventos Disponíveis
Eventos de Pagamento
| Evento | Descrição |
|---|---|
PAYMENT_STATUS_UPDATED | Um pagamento Pix mudou de status |
PAYMENT_COMPLETED | Pagamento liquidado com sucesso (ACSC) |
PAYMENT_REJECTED | Pagamento rejeitado (RJCT) |
Eventos de Consentimento
| Evento | Descrição |
|---|---|
CONSENT_STATUS_UPDATED | O consentimento mudou de status |
CONSENT_AUTHORISED | Consentimento autorizado pelo usuário |
CONSENT_CONSUMED | Consentimento consumido (limite total atingido) |
CONSENT_REVOKED | Consentimento revogado |
CONSENT_EXPIRED | Consentimento expirou por prazo ou limite |
Estrutura do Payload
Webhook de Pagamento
{
"event": "PAYMENT_STATUS_UPDATED",
"timestamp": "2026-02-18T10:01:30Z",
"data": {
"paymentInitiationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"paymentId": "pmt-abc123def456",
"endToEndId": "E13935893202602180000AbCdEfGhIjK",
"previousStatus": "PDNG",
"currentStatus": "ACSC",
"amount": "0.01",
"currency": "BRL",
"date": "2026-02-18",
"updatedAt": "2026-02-18T10:01:28Z",
"rejectionReason": null
}
}| Campo | Tipo | Descrição |
|---|---|---|
event | string | Tipo do evento |
timestamp | string | Data/hora do disparo do webhook (ISO 8601 UTC) |
data.paymentInitiationId | string | ID da payment initiation associada |
data.paymentId | string | ID único do pagamento |
data.endToEndId | string | EndToEndId da transação Pix |
data.previousStatus | string | Status anterior do pagamento |
data.currentStatus | string | Novo status do pagamento |
data.amount | string | Valor do pagamento em BRL |
data.rejectionReason | string | null | Motivo da rejeição (somente quando currentStatus = RJCT) |
Webhook de Consentimento
{
"event": "CONSENT_STATUS_UPDATED",
"timestamp": "2026-02-12T10:05:00Z",
"data": {
"paymentInitiationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"consentId": "urn:celcoin:a1b2c3d4-e5f6...",
"previousStatus": "AWAITING_AUTHORISATION",
"currentStatus": "AUTHORISED",
"updatedAt": "2026-02-12T10:04:58Z",
"expiresAt": "2028-02-12T00:00:00Z",
"totalAllowedAmount": "10000.00",
"totalUsedAmount": "0.00"
}
}| Campo | Tipo | Descrição |
|---|---|---|
data.paymentInitiationId | string | ID da payment initiation |
data.consentId | string | URN do consentimento na detentora |
data.previousStatus | string | Status anterior do consentimento |
data.currentStatus | string | Novo status do consentimento |
data.expiresAt | string | Data de expiração do consentimento |
data.totalUsedAmount | string | Total acumulado utilizado no momento do evento |
Status de Pagamento
| Status | Descrição |
|---|---|
PDNG | Pendente — aguardando processamento |
SCHD | Agendado |
CANC | Cancelado |
ACSP | Aceito para processamento pelo Pix |
ACSC | Liquidado com sucesso (Settlement Completed) |
RJCT | Rejeitado |
Status de Consentimento
| Status | Descrição |
|---|---|
AWAITING_AUTHORISATION | Aguardando autorização do usuário |
AUTHORISED | Consentimento ativo e autorizado |
CONSUMED | Limite total (totalAllowedAmount) atingido |
REVOKED | Revogado pelo usuário ou pela ITP |
EXPIRED | Expirado por prazo de vigência |
Exemplo de Implementação do Endpoint Receptor
// Node.js / Express
app.post('/webhooks/sweeping-accounts', async (req, res) => {
// Responder imediatamente com 200 para confirmar recebimento
res.status(200).json({ received: true });
const { event, data } = req.body;
try {
switch (event) {
case 'PAYMENT_STATUS_UPDATED':
await handlePaymentStatusUpdate(data);
break;
case 'CONSENT_STATUS_UPDATED':
await handleConsentStatusUpdate(data);
break;
case 'CONSENT_CONSUMED':
await handleConsentConsumed(data);
break;
case 'CONSENT_REVOKED':
await handleConsentRevoked(data);
break;
}
} catch (error) {
// Log o erro, mas NÃO retorne erro HTTP — o webhook já foi confirmado
console.error('Erro ao processar webhook:', error);
}
});Importante: Responda
HTTP 200antes de processar o evento. O processamento deve ser assíncrono para não ultrapassar o timeout.
Segurança
Validação de Assinatura
Os webhooks enviados pela Celcoin incluem uma assinatura no header para garantir autenticidade:
x-celcoin-signature: sha256=abc123...
Valide a assinatura antes de processar o evento:
const crypto = require('crypto');
function validateWebhookSignature(payload, signature, secret) {
const expectedSig = 'sha256=' +
crypto.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSig)
);
}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
A mesma notificação pode ser entregue mais de uma vez (retries). Utilize o x-celcoin-event-id para deduplicação:
const processedEvents = new Set(); // Use Redis em produção
app.post('/webhooks/sweeping-accounts', (req, res) => {
const eventId = req.headers['x-celcoin-event-id'];
if (processedEvents.has(eventId)) {
return res.status(200).json({ received: true, duplicate: true });
}
processedEvents.add(eventId);
res.status(200).json({ received: true });
processEvent(req.body);
});Pontos de Atenção
Responda200imediatamente: Seu endpoint tem um timeout curto. Processe o evento de forma assíncrona (fila, worker) e responda200antes de fazer qualquer lógica. Respostas lentas causam retries desnecessários.
Não confie apenas em webhooks: Webhooks podem falhar ou chegar com atraso. Para operações críticas, combine webhooks com polling periódico como fallback.
Valide a assinatura: Sempre valide o headerx-celcoin-signatureantes de processar o payload. Isso previne eventos falsos (spoofing).
HTTPS obrigatório: O endpoint de webhook deve usar HTTPS com certificado válido. Endpoints HTTP são rejeitados.
CONSENT_CONSUMEDvsCONSENT_REVOKED: São eventos distintos.CONSUMEDsignifica que o limite foi atingido (ciclo natural);REVOKEDsignifica cancelamento explícito. Trate cada um de forma adequada na experiência do usuário.
Pagamentos comRJCT: Ao receberPAYMENT_STATUS_UPDATEDcomcurrentStatus: RJCT, verifique o camporejectionReasonpara determinar se é possível fazer retry. Erros comoPAGAMENTO_DIVERGENTE_CONSENTIMENTOindicam problemas no consentimento; erros de saldo ou indisponibilidade podem ser retriados dentro das janelas permitidas.
SLA regulatório: O Open Finance Brasil exige que a detentora notifique em até 1,5 segundo. Se seu sistema não receber notificações dentro de um tempo razoável, implemente polling como contingência e reporte o SLA ao time de suporte.