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 200 em 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:

EventoSLA 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

EventoDescrição
PAYMENT_STATUS_UPDATEDUm pagamento Pix mudou de status
PAYMENT_COMPLETEDPagamento liquidado com sucesso (ACSC)
PAYMENT_REJECTEDPagamento rejeitado (RJCT)

Eventos de Consentimento

EventoDescrição
CONSENT_STATUS_UPDATEDO consentimento mudou de status
CONSENT_AUTHORISEDConsentimento autorizado pelo usuário
CONSENT_CONSUMEDConsentimento consumido (limite total atingido)
CONSENT_REVOKEDConsentimento revogado
CONSENT_EXPIREDConsentimento 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
  }
}
CampoTipoDescrição
eventstringTipo do evento
timestampstringData/hora do disparo do webhook (ISO 8601 UTC)
data.paymentInitiationIdstringID da payment initiation associada
data.paymentIdstringID único do pagamento
data.endToEndIdstringEndToEndId da transação Pix
data.previousStatusstringStatus anterior do pagamento
data.currentStatusstringNovo status do pagamento
data.amountstringValor do pagamento em BRL
data.rejectionReasonstring | nullMotivo 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"
  }
}
CampoTipoDescrição
data.paymentInitiationIdstringID da payment initiation
data.consentIdstringURN do consentimento na detentora
data.previousStatusstringStatus anterior do consentimento
data.currentStatusstringNovo status do consentimento
data.expiresAtstringData de expiração do consentimento
data.totalUsedAmountstringTotal acumulado utilizado no momento do evento

Status de Pagamento

StatusDescrição
PDNGPendente — aguardando processamento
SCHDAgendado
CANCCancelado
ACSPAceito para processamento pelo Pix
ACSCLiquidado com sucesso (Settlement Completed)
RJCTRejeitado

Status de Consentimento

StatusDescrição
AWAITING_AUTHORISATIONAguardando autorização do usuário
AUTHORISEDConsentimento ativo e autorizado
CONSUMEDLimite total (totalAllowedAmount) atingido
REVOKEDRevogado pelo usuário ou pela ITP
EXPIREDExpirado 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 200 antes 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

HeaderDescrição
Content-Typeapplication/json
x-celcoin-signatureAssinatura HMAC-SHA256 do payload
x-celcoin-event-idID único do evento (para deduplicação)
x-celcoin-timestampTimestamp 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

⚠️

Responda 200 imediatamente: Seu endpoint tem um timeout curto. Processe o evento de forma assíncrona (fila, worker) e responda 200 antes 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 header x-celcoin-signature antes 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_CONSUMED vs CONSENT_REVOKED: São eventos distintos. CONSUMED significa que o limite foi atingido (ciclo natural); REVOKED significa cancelamento explícito. Trate cada um de forma adequada na experiência do usuário.

⚠️

Pagamentos com RJCT: Ao receber PAYMENT_STATUS_UPDATED com currentStatus: RJCT, verifique o campo rejectionReason para determinar se é possível fazer retry. Erros como PAGAMENTO_DIVERGENTE_CONSENTIMENTO indicam 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.