Pular para o conteúdo principal

Webhook

A Bitpayway envia um webhook quando uma ordem de pagamento muda para um dos seguintes status:

  • partial paid
  • paid
  • expired partial paid
  • expired
  • canceled

Nenhum webhook é enviado para o status inicial pending na criação da ordem, nem enquanto um pagamento que completa a ordem aguarda as confirmações de bloco (paid waiting confirmations): você é notificado quando a ordem fica paid. A única exceção são as ordens multidestino: uma ordem multidestino cujo settlementStatus mudou para mismatch é notificada mesmo que o status ainda seja pending ou paid waiting confirmations.

Para evitar uma requisição HTTP separada para cada ordem processada, as notificações são agrupadas por negócio antes do envio, com até 30 notificações em cada requisição. Por isso, o corpo da requisição é sempre um array JSON, mesmo quando há apenas uma notificação.

Status de uma ordem​

A tabela abaixo lista todos os status que uma ordem pode ter, inclusive os que não disparam webhook. Eles podem aparecer ao consultar uma ordem pela API e no campo status de uma notificação.

StatusSignificado
pendingAguardando pagamento.
partial paidParte do valor foi paga e confirmada.
paid waiting confirmationsO valor foi pago e aguarda as confirmações de bloco.
paidO valor foi pago e confirmado.
expiredOs 30 minutos terminaram sem pagamento.
expired partial paidOs 30 minutos terminaram com só parte do valor paga.
canceledA ordem foi cancelada.

Uma ordem aceita pagamentos por 30 minutos depois de criada. Uma ordem que não foi paga por completo nesse prazo fica expired, ou expired partial paid se recebeu parte do valor.

Requisição HTTP​

Método: POST

Headers:

  • Content-Type: application/json
  • SPP-Api-Timestamp: timestamp Unix em milissegundos que indica quando a requisição foi enviada.
  • SPP-Api-Signature: assinatura HMAC-SHA256 no formato sha256=<digest hexadecimal>.

Body:

O corpo é um array de notificações. Cada notificação tem notification: 'order' e um objeto data com a ordem. O formato de data depende do modelo de pagamento da ordem:

  • Ordem tradicional: data contém apenas os campos comuns. O campo paymentModel não está presente.
  • Ordem multidestino: data contém os campos comuns mais os campos multidestino, e paymentModel é 'split'.

Use a presença de paymentModel === 'split' para distinguir os dois casos. Uma integração que não usa pagamentos multidestino pode ignorar os campos adicionais.

type WebhookBody = OrderNotification[];

type OrderNotification = {
notification: 'order';
data: StandardOrderData | MultiDestinationOrderData;
};

// Campos presentes em toda notificação.
type StandardOrderData = {
id: string;
requestId: string;
description: string;
amount: string;
status: OrderStatus;
network: string;
currency: string;
paidAmount: string;
netAmount: string;
fee: string;
};

// Ordem multidestino: todos os campos comuns + os campos abaixo.
type MultiDestinationOrderData = StandardOrderData & {
paymentModel: 'split';
settlementStatus: 'pending' | 'matched' | 'mismatch';
settlementVersion: number;
split: {
partnerId: string;
partnerCodeId: string;
partnerAmount: string;
};
partnerNetAmount: string;
partnerGrossAmount: string;
clientCommission: string;
partnerCommission: string;
commissionReceived: string;
};

type OrderStatus =
| 'pending' // apenas em ordens multidestino em `mismatch`
| 'paid waiting confirmations' // apenas em ordens multidestino em `mismatch`
| 'partial paid'
| 'paid'
| 'expired partial paid'
| 'expired'
| 'canceled';

Campos comuns​

Presentes em todas as notificações, de ordens tradicionais e multidestino.

CampoDescrição
idUUID gerado pela Bitpayway para a ordem de pagamento.
requestIdUUID informado por sua aplicação ao criar a ordem.
descriptionDescrição informada na criação da ordem.
amountValor solicitado para o pagamento, representado como string decimal.
statusStatus atual da ordem detectado pela Bitpayway.
networkNome da rede blockchain.
currencySímbolo do token.
paidAmountValor total pago na blockchain antes das taxas, representado como string decimal.
netAmountValor enviado para a sua carteira após as taxas de pagamento, representado como string decimal.
feeSpread da plataforma, representado como string decimal. Em ordens multidestino, é a soma de clientCommission e partnerCommission.

Todos os valores monetários são strings decimais já convertidas para a unidade do token (por exemplo, "1000.52" USDC), não valores brutos em wei.

Exemplo: ordem tradicional​

[
{
"notification": "order",
"data": {
"id": "45ccdc4c-8457-4e9c-a16d-4c9d35f128ed",
"requestId": "2f3c9d2c-1b75-4e9d-9b2f-67d0a9c3c7d1",
"description": "Pedido #1234",
"amount": "1000.52",
"status": "partial paid",
"network": "ethereum",
"currency": "USDC",
"paidAmount": "1000",
"netAmount": "950",
"fee": "50"
}
}
]

Ordens multidestino​

Uma ordem multidestino envia a mesma notificação, acrescida dos campos abaixo. Eles são ausentes nas ordens tradicionais, então uma integração existente continua funcionando sem alteração.

CampoDescrição
paymentModelSempre split. Ausente nas ordens tradicionais.
settlementStatuspending, matched ou mismatch. Uma ordem em mismatch exige conciliação com o parceiro. É independente do status da ordem.
settlementVersionVersão dos valores confirmados; aumenta sempre que eles mudam.
split.partnerIdUUID do vínculo do parceiro.
split.partnerCodeIdIdentificador on-chain do vínculo do parceiro.
split.partnerAmountParcela bruta acordada na criação da ordem.
netAmountValor efetivamente recebido por você.
partnerNetAmountValor efetivamente recebido pelo parceiro.
partnerGrossAmountValor bruto acumulado destinado ao parceiro.
clientCommissionComissão nominal sobre a sua parcela.
partnerCommissionComissão nominal sobre a parcela do parceiro.
commissionReceivedValor efetivamente recebido pela plataforma.

Exemplo: ordem multidestino​

[
{
"notification": "order",
"data": {
"id": "45ccdc4c-8457-4e9c-a16d-4c9d35f128ed",
"requestId": "2f3c9d2c-1b75-4e9d-9b2f-67d0a9c3c7d1",
"description": "Pedido #1234",
"amount": "1000",
"status": "paid",
"network": "ethereum",
"currency": "USDC",
"paidAmount": "1000",
"netAmount": "665",
"fee": "50",
"paymentModel": "split",
"settlementStatus": "matched",
"settlementVersion": 1,
"split": {
"partnerId": "9b1d2c3e-4f50-4a6b-8c7d-0e1f2a3b4c5d",
"partnerCodeId": "12",
"partnerAmount": "300"
},
"partnerNetAmount": "285",
"partnerGrossAmount": "300",
"clientCommission": "35",
"partnerCommission": "15",
"commissionReceived": "50"
}
}
]

Quando uma ordem multidestino é notificada​

Além das mudanças de status, uma ordem multidestino também é notificada sempre que o settlementStatus ou os valores confirmados mudam — o que incrementa o settlementVersion. Isso tem duas consequências:

  • Uma notificação pode chegar depois de um status terminal, então uma ordem multidestino pode ser notificada mais de uma vez e o status pode se repetir.
  • Uma ordem em settlementStatus: "mismatch" é notificada mesmo com status: "pending", para que você possa iniciar a conciliação antes de qualquer confirmação de pagamento.

Considere o settlementVersion parte da sua chave de idempotência para as atualizações financeiras e não repita a entrega comercial a cada atualização. Uma resposta a uma notificação antiga não confirma a entrega de uma versão mais recente.

Veja Pagamentos multidestino para entender como os valores são calculados e quando uma ordem é considerada aprovada.

Entrega e novas tentativas​

Sua aplicação deve processar todos os itens do array e retornar uma resposta 2xx após aceitar as notificações.

O resultado de uma requisição HTTP se aplica ao lote completo:

  • Uma resposta 2xx bem-sucedida marca como notificadas todas as ordens cujo status não mudou desde a criação do lote.
  • Um timeout, erro de conexão ou resposta diferente de 2xx mantém o lote completo elegível para uma nova tentativa.

A Bitpayway realiza até cinco tentativas de entrega. Como uma requisição pode ser processada pelo seu endpoint sem que a Bitpayway receba uma resposta bem-sucedida, uma nova tentativa pode entregar notificações duplicadas. Processe as notificações de forma idempotente, utilizando em conjunto o id e o status da ordem como chave de idempotência.

Verificação da assinatura​

A assinatura é calculada a partir do corpo bruto exato da requisição:

<timestamp>\n<método HTTP em maiúsculas>\n<corpo bruto da requisição>

No webhook, o método HTTP é POST. A URL e o path não fazem parte do conteúdo assinado.

Utilize como segredo HMAC a chave secreta de webhook gerada nas configurações do negócio. Capture o corpo bruto antes de interpretá-lo como JSON; serializar novamente o JSON já processado pode produzir bytes diferentes e invalidar a assinatura.

O exemplo abaixo verifica a assinatura utilizando Node.js:

import crypto from 'node:crypto';

const verifyHmacSignature = (params: {
apiSecret: string;
timestampHeader: string | undefined;
signatureHeader: string | undefined;
method: string;
rawBody: string;
maxSkewMs?: number;
}) => {
const {
apiSecret,
timestampHeader,
signatureHeader,
method,
rawBody,
maxSkewMs = 1000 * 60 * 5, // 5 minutos
} = params;

if (!timestampHeader || !signatureHeader) {
return false;
}

const timestamp = Number(timestampHeader);
if (!Number.isFinite(timestamp)) {
return false;
}

// Proteção contra ataques de repetição.
if (Math.abs(Date.now() - timestamp) > maxSkewMs) {
return false;
}

const expectedPrefix = 'sha256=';
if (!signatureHeader.startsWith(expectedPrefix)) {
return false;
}

const receivedHmacHex = signatureHeader.slice(expectedPrefix.length);
const stringToSign = [timestamp.toString(), method.toUpperCase(), rawBody].join('\n');
const calculatedHmacHex = crypto
.createHmac('sha256', apiSecret)
.update(stringToSign, 'utf8')
.digest('hex');

const receivedBuffer = Buffer.from(receivedHmacHex, 'hex');
const calculatedBuffer = Buffer.from(calculatedHmacHex, 'hex');

if (receivedBuffer.length !== calculatedBuffer.length) {
return false;
}

return crypto.timingSafeEqual(receivedBuffer, calculatedBuffer);
};

A tolerância de cinco minutos deste exemplo é uma política do cliente e pode ser ajustada. É altamente recomendável validar o timestamp para reduzir o risco de ataques de repetição.