Webhooks

A BSPAY envia eventos via POST para a postback_url configurada na credencial (default) ou na própria transação (override). Você usa esses eventos pra liberar acesso, atualizar status e disparar notificações.

A BSPAY envia eventos via POST para a postback_url configurada na credencial (default) ou na própria transação (override). Você usa esses eventos pra liberar acesso, atualizar status e disparar notificações.

Headers enviados

Content-Type:        application/json
X-Webhook-Event:     <nome do evento>
X-Webhook-Signature: hex(hmac_sha256(rawBody, webhook_secret))
X-Webhook-Id:        evt_<random>
X-Webhook-Timestamp: <unix_seconds>
User-Agent:          BSPay-Webhook/2.0

Validar a assinatura HMAC

Toda requisição vem assinada com o seu webhook_secret (definido no Dashboard ao habilitar webhooks). Recalcule o HMAC sobre o body raw e compare com X-Webhook-Signature antes de processar — se não bater, rejeite com 401.

PHP
Node.js / Express
Python / Flask
Go
<?php
$rawBody = file_get_contents('php://input');
$sig     = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$ts      = (int) ($_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? 0);

// 1) Janela ±5min anti-replay
if (abs(time() - $ts) > 300) {
    http_response_code(401);
    exit;
}

// 2) HMAC SHA256 timing-safe
$expected = hash_hmac('sha256', $rawBody, $WEBHOOK_SECRET);
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit;
}

$payload = json_decode($rawBody, true);
// processar...
http_response_code(200);
echo json_encode(['received' => true]);

Envelope V2

Todo payload chega no formato:

{
  "event": "cashin.confirmed",
  "timestamp": "2026-05-01T12:34:56-03:00",
  "transaction_id": "abc123def456",
  "data": {
    "transaction_id": "abc123def456",
    "amount": "...",
    "..."
  }
}
CampoDescrição
eventNome canônico do evento em dot notation
timestampISO 8601 com timezone — momento que o webhook foi gerado
transaction_idEspelho do data.transaction_id no nível raiz (idempotência mais simples)
dataPayload específico do evento (varia conforme o tipo)

Política de retry

Se seu endpoint não responder com HTTP 2xx (timeout, 5xx, DNS fail), reentregamos com backoff exponencial:

TentativaDelay após anterior
1 (síncrono no momento do evento)
2+1s
3+5s
4+30s
5+120s
6 (final)+600s

Após 5 retries falhos, paramos de tentar. Solicite reentrega manual via Dashboard ou suporte.

Implementação no seu lado:

  • Responda HTTP 200 o quanto antes (idealmente <2s). Processamento pesado deve ser assíncrono.
  • Trate idempotência via transaction_id ou X-Webhook-Id — duplicate delivery pode ocorrer.
  • URL deve ser HTTPS com certificado válido. http:// ou self-signed são rejeitadas.
  • Limite: 10s timeout total + 5s connect timeout.

Rate limit outbound

Pra evitar bursts no seu endpoint em picos de transação:

  • 100 webhooks/minuto por integration (sliding window 60s)
  • Quando excede, webhooks adicionais são enfileirados e reentregues após +120s, mantendo a ordem aproximada
  • Você não perde eventos — apenas alguns chegam atrasados em picos extremos

Throughput maior? Fale com suporte.


Eventos disponíveis

EventoQuando dispara
cashin.confirmedPIX/cripto/SPEI recebido (cobrança ou wallet fixa)
cashin.refundedCashin estornado (refund manual ou chargeback perdido)
cashin.expiredQR PIX expirou sem pagamento
cashout.confirmedSaque liquidado com sucesso
cashout.failedSaque rejeitado pelo gateway/provider — saldo refundado automaticamente
cashout.refundedSaque estornado pelo provider após confirmação
transfer.confirmedTransferência interna concluída
conversion.confirmedConversão FX completada
conversion.refundedConversão estornada (operação manual de suporte)
wallet.createdCarteira fixa provisionada
chargeback.openedDisputa MED PIX aberta pelo pagador
chargeback.respondedMerchant respondeu à disputa
chargeback.confirmedChargeback confirmado (devolução efetuada)
chargeback.wonMerchant ganhou a disputa (defesa aceita)
chargeback.lostMerchant perdeu a disputa (defesa rejeitada)
chargeback.canceledDisputa cancelada pelo pagador

Payloads

cashin.confirmed

Pagador completou a cobrança (PIX dinâmico/estático, cripto temporário/wallet fixa, SPEI). Saldo já creditado.

PIX BRL
Cripto USDT TRC20
Wallet fixa (source: static)
SPEI MXN
{
  "event": "cashin.confirmed",
  "timestamp": "2026-05-01T13:00:04-03:00",
  "transaction_id": "abc123",
  "data": {
    "transaction_id": "abc123",
    "external_id": "order_xyz",
    "type": "cashin",
    "currency": "BRL",
    "currency_type": "fiat",
    "amount": "100.00",
    "fee": "1.50",
    "amount_net": "98.50",
    "status": "confirmed",
    "source": "dynamic_qr",
    "network": "PIX",
    "e2e_id": "E17028875202604301400ABC123",
    "payer": { "name": "João Silva", "document": "12345678901" },
    "old_balance": "500.00",
    "new_balance": "598.50",
    "confirmed_at": "2026-05-01T13:00:04Z"
  }
}

sourcedynamic_qr para cobranças geradas via cashin, static para depósitos em carteira fixa.

cashin.refunded

Cashin revertido por qualquer causa (refund manual, falha do provider, ou chargeback perdido). Saldo debitado.

{
  "event": "cashin.refunded",
  "timestamp": "2026-05-01T14:00:00-03:00",
  "transaction_id": "abc123",
  "data": {
    "transaction_id": "abc123",
    "original_transaction_id": "abc123",
    "external_id": "order_xyz",
    "currency": "BRL",
    "amount": "100.00",
    "amount_refunded": "100.00",
    "status": "refunded",
    "reason": "PROVIDER_REVERSAL",
    "old_balance": "598.50",
    "new_balance": "498.50",
    "refunded_at": "2026-05-01T14:00:00Z"
  }
}

cashin.expired

QR PIX expirou sem pagamento (não houve crédito). Bom pra limpar pedidos pendentes na sua DB.

{
  "event": "cashin.expired",
  "timestamp": "2026-05-01T14:30:00-03:00",
  "transaction_id": "abc123",
  "data": {
    "transaction_id": "abc123",
    "external_id": "order_xyz",
    "currency": "BRL",
    "amount": "100.00",
    "status": "expired",
    "expired_at": "2026-05-01T14:30:00Z"
  }
}

cashout.confirmed

Saque processado pelo provider (PIX liquidado, tx broadcast on-chain, SPEI confirmado).

PIX BRL
Cripto USDT TRC20
SPEI MXN
{
  "event": "cashout.confirmed",
  "timestamp": "2026-05-01T13:00:04-03:00",
  "transaction_id": "abc123",
  "data": {
    "transaction_id": "abc123",
    "external_id": "withdraw_001",
    "type": "cashout",
    "currency": "BRL",
    "currency_type": "fiat",
    "network": "PIX",
    "amount": "50.00",
    "fee": "1.00",
    "status": "confirmed",
    "wallet": "12345678901",
    "key_type": "cpf",
    "hash": "E17028875202604301400ABC123",
    "receiver": {
      "name": "João Silva",
      "document": "12345678901",
      "bank": "BCO DO BRASIL S.A."
    },
    "confirmed_at": "2026-05-01T13:00:04Z"
  }
}

cashout.failed

Saque rejeitado pelo gateway/provider. Saldo refundado automaticamente — você não precisa estornar manualmente.

{
  "event": "cashout.failed",
  "timestamp": "2026-05-01T13:05:00-03:00",
  "transaction_id": "abc123",
  "data": {
    "transaction_id": "abc123",
    "external_id": "withdraw_001",
    "type": "cashout",
    "currency": "USDT",
    "network": "TRC20",
    "amount": "50.00",
    "status": "failed",
    "wallet": "TRwLh...",
    "error_code": "TRON_ENERGY_FAIL",
    "error_message": "Insufficient energy on hot wallet to broadcast.",
    "refunded_amount": "51.50",
    "failed_at": "2026-05-01T13:05:00Z"
  }
}
error_codeSignificado
TRON_ENERGY_FAILHot wallet sem energia/bandwidth para broadcast
INSUFFICIENT_BALANCEHot wallet sem saldo on-chain
NO_HOT_WALLETNenhuma wallet quente disponível para esta chain
INVALID_ADDRESSEndereço destino inválido (checksum/formato)
BLACKLISTEDEndereço destino na blacklist (sanção/AML)
BLOCKCHAIN_REVERTTx revertida no bloco
BROADCAST_FAILFalha ao enviar tx para a rede
PIX_KEY_NOT_FOUNDChave PIX não encontrada no DICT
PROVIDER_TIMEOUTProvider PIX/SPEI timeout
UNKNOWNErro não categorizado — contate o suporte

cashout.refunded

Saque estornado pelo provider após já ter sido confirmado (caso raro — geralmente devolução pela instituição destino). Saldo creditado de volta.

{
  "event": "cashout.refunded",
  "timestamp": "2026-05-01T14:00:00-03:00",
  "transaction_id": "abc123",
  "data": {
    "transaction_id": "abc123",
    "original_transaction_id": "abc123",
    "external_id": "withdraw_001",
    "currency": "BRL",
    "network": "PIX",
    "amount": "50.00",
    "amount_refunded": "50.00",
    "status": "refunded",
    "reason": "PROVIDER_REVERSAL",
    "refunded_at": "2026-05-01T14:00:00Z"
  }
}

transfer.confirmed

Transferência interna concluída. Ambas as contas (remetente e destinatário) recebem este evento.

Recebida (no destinatário)
Enviada (no remetente)
{
  "event": "transfer.confirmed",
  "timestamp": "2026-05-01T13:00:00-03:00",
  "transaction_id": "tx_abc123",
  "data": {
    "transaction_id": "tx_abc123",
    "direction": "received",
    "amount": "25.00",
    "currency": "BRL",
    "counterparty": "sender_username",
    "description": "Pagamento parceiro",
    "old_balance": "100.00",
    "new_balance": "125.00",
    "confirmed_at": "2026-05-01T13:00:00Z"
  }
}

conversion.confirmed

Conversão FX executada — saldo de origem debitado e destino creditado.

{
  "event": "conversion.confirmed",
  "timestamp": "2026-05-01T13:00:00-03:00",
  "transaction_id": "conv_abc",
  "data": {
    "conversion_id": "conv_abc",
    "external_id": "conv_001",
    "amount_from": "80.00",
    "currency_from": "USDT",
    "amount_to": "432.00",
    "currency_to": "BRL",
    "rate": "5.4000",
    "fee": "1.50",
    "status": "completed",
    "completed_at": "2026-05-01T13:00:00Z"
  }
}

conversion.refunded

Conversão estornada via operação manual do suporte (raro — casos de erro ou fraude).

{
  "event": "conversion.refunded",
  "timestamp": "2026-05-01T15:00:00-03:00",
  "transaction_id": "conv_abc",
  "data": {
    "conversion_id": "conv_abc",
    "original_conversion_id": "conv_abc",
    "amount_reverted_from": "432.00",
    "currency_reverted_from": "BRL",
    "amount_returned_to": "80.00",
    "currency_returned_to": "USDT",
    "reason": "SUPPORT_MANUAL_REVERSAL",
    "refunded_at": "2026-05-01T15:00:00Z"
  }
}

wallet.created

Carteira fixa provisionada (chave PIX estática, CLABE permanente ou endereço cripto on-chain).

{
  "event": "wallet.created",
  "timestamp": "2026-05-01T13:00:00-03:00",
  "transaction_id": "wallet_41",
  "data": {
    "wallet_id": 41,
    "currency": "USDT",
    "currency_type": "crypto",
    "chain": "tron",
    "network": "TRC20",
    "address": "TWMrdKSXaTbpcjBAT5PEzL4LtkC9fL4HQk",
    "min_confirmations": 20,
    "created_at": "2026-05-01T13:00:00Z"
  }
}

chargeback.opened

Disputa MED PIX aberta pelo pagador (ver Disputas). Saldo bloqueado automaticamente. Você tem 7 dias pra responder via /v2/account/infractions/reply.

{
  "event": "chargeback.opened",
  "timestamp": "2026-05-01T13:00:00-03:00",
  "transaction_id": "tx_xyz789",
  "data": {
    "infraction_id": "abc-uuid-1234",
    "transaction_id": "tx_xyz789",
    "type": "REFUND_REQUEST",
    "status": "open",
    "amount": "25.00",
    "currency": "BRL",
    "e2e_id": "E17028875202604301400ABC123",
    "deadline_at": "2026-05-08T13:00:00Z",
    "reason": "Customer disputed — alleged unauthorized payment",
    "created_at": "2026-05-01T13:00:00Z"
  }
}

chargeback.responded

Sua resposta foi registrada via /v2/account/infractions/reply. Aguarde decisão do banco/PSP.

{
  "event": "chargeback.responded",
  "timestamp": "2026-05-01T15:00:00-03:00",
  "transaction_id": "tx_xyz789",
  "data": {
    "infraction_id": "abc-uuid-1234",
    "transaction_id": "tx_xyz789",
    "reply_id": 60,
    "status": "responded",
    "responded_at": "2026-05-01T15:00:00Z"
  }
}

chargeback.confirmed

Chargeback finalizado — dinheiro devolvido ao pagador. Disparado junto com cashin.refunded quando a origem é uma disputa MED.

{
  "event": "chargeback.confirmed",
  "timestamp": "2026-05-05T10:00:00-03:00",
  "transaction_id": "tx_xyz789",
  "data": {
    "infraction_id": "abc-uuid-1234",
    "transaction_id": "tx_xyz789",
    "amount": "25.00",
    "currency": "BRL",
    "confirmed_at": "2026-05-05T10:00:00Z"
  }
}

chargeback.won

Defesa aceita pelo PSP. Saldo desbloqueado e mantido na sua conta.

{
  "event": "chargeback.won",
  "timestamp": "2026-05-05T10:00:00-03:00",
  "transaction_id": "tx_xyz789",
  "data": {
    "infraction_id": "abc-uuid-1234",
    "transaction_id": "tx_xyz789",
    "amount": "25.00",
    "currency": "BRL",
    "resolved_at": "2026-05-05T10:00:00Z"
  }
}

chargeback.lost

Defesa rejeitada. Saldo é debitado e devolvido ao pagador. cashin.refunded também dispara.

{
  "event": "chargeback.lost",
  "timestamp": "2026-05-05T10:00:00-03:00",
  "transaction_id": "tx_xyz789",
  "data": {
    "infraction_id": "abc-uuid-1234",
    "transaction_id": "tx_xyz789",
    "amount": "25.00",
    "currency": "BRL",
    "amount_refunded": "25.00",
    "resolved_at": "2026-05-05T10:00:00Z"
  }
}

chargeback.canceled

Disputa cancelada pelo próprio pagador antes da decisão. Saldo desbloqueado.

{
  "event": "chargeback.canceled",
  "timestamp": "2026-05-03T10:00:00-03:00",
  "transaction_id": "tx_xyz789",
  "data": {
    "infraction_id": "abc-uuid-1234",
    "transaction_id": "tx_xyz789",
    "amount": "25.00",
    "currency": "BRL",
    "canceled_by": "payer",
    "canceled_at": "2026-05-03T10:00:00Z"
  }
}

Boas práticas

  1. Valide a assinatura HMAC sempre — sem isso, qualquer um pode forjar webhooks
  2. Responda HTTP 200 imediatamente — processe em background (queue, async task)
  3. Idempotência via X-Webhook-Id ou transaction_id — o mesmo evento pode chegar 2+ vezes após retry
  4. Logue request_id quando reportar bugs ao suporte
  5. Reconcile periodicamente via POST /v2/account/transactions/list — webhook não é fonte única de verdade
  6. Sandbox — rejeite explicitamente eventos com X-Sandbox: 1 no seu sistema de produção

Esta página foi útil?