Webhook
Os webhooks permitem que seu sistema receba notificações automáticas em tempo real do PayerScan. O sistema envia requisições POST para a callback_url fornecida pelo Merchant ao criar uma fatura.
Tipos de Eventos
O PayerScan possui 2 eventos principais retornados no campo status:
completed— Pagamento bem-sucedido: Uma transação correspondente foi confirmada com a fatura (blockchain ou Binance Pay).expired— Fatura expirada: A fatura excedeu o tempo de espera para pagamento.
Os comerciantes precisam expor uma URL pública (HTTPS recomendado), receber requisições POST, processar o payload e retornar HTTP 2xx dentro do timeout especificado (ex.: 10 segundos).
Estrutura do Payload
1. Webhook em pagamento bem-sucedido (status: completed)
Quando uma transação correspondente é encontrada (endereço correto, valor correto, dentro da janela de tempo), o sistema atualiza a fatura para completed e envia um POST para callback_url com este payload:
Payload (JSON body)
{
"merchant_id": "MERCHANT_001",
"api_key": "YOUR_API_KEY",
"request_id": "order-1234",
"trans_id": "TID-ABC123DEF4567890",
"status": "completed",
"amount": "100",
"token_symbol": "USDT",
"token_price": "1",
"token_amount": "100.0026",
"network_symbol": "BSC",
"from_address": "0x8894e0a0c962cb723c1976a4421c95949be2d4e3",
"to_address": "0xc221460115e2CfCa5bF089A7e647b11cb9631efE",
"transaction_hash": "0xd346b32b83b35376d42a2464598fbf565fffb39e6569200f034a5e8342c532d7"
}
| Campo | Descrição |
|---|---|
merchant_id | Código do comerciante da loja. |
api_key | API Key (para sua verificação cruzada, se necessário). |
request_id | Seu ID de pedido (para comparar com sua fatura interna). |
trans_id | Código da fatura. Use para atualizar o pedido correspondente. |
status | Sempre "completed". |
amount | Valor em USD (string). |
token_symbol | Moeda/Token (ex.: USDT). |
token_price | Taxa de câmbio no momento da transação (string). Pode usar amount como fallback se indisponível. |
token_amount | Quantidade real de crypto transferida (string). Pode ser null em casos raros. |
network_symbol | Rede (ex.: BSC, TRC20, ETH, SOL, BINANCE_ID, OKX_UID, BYBIT_UID, ...). |
from_address | Endereço da carteira do remetente. |
to_address | Endereço da carteira de recebimento (da loja). |
transaction_hash | Hash da transação na blockchain. |
Requisitos do Merchant
- Receba o POST no endpoint correspondente ao
callback_url. - Analise o JSON body.
- Verifique (opcional, mas recomendado): compare
merchant_idoutrans_idcom seus dados internos. - Atualize o pedido: marque o pedido correspondente a
trans_idourequest_idcomo pago; salvetransaction_hashse necessário. - Retorne HTTP 2xx (ex.: 200 OK) dentro do timeout (ex.: 10 segundos). O corpo da resposta não é obrigatório; o sistema precisa apenas de um status 2xx para considerar o webhook bem-sucedido.
Se você retornar 4xx/5xx ou timeout, o sistema marca o callback como failed e possui um mecanismo de retentativa (o número de tentativas e intervalos dependem da configuração do backend).
2. Webhook na expiração da fatura (status: expired)
Quando uma fatura muda para o estado expired (ultrapassando expires_at), o sistema envia um POST para callback_url com um payload mais simples:
Payload (JSON body)
{
"merchant_id": "MERCHANT_001",
"request_id": "order-1234",
"trans_id": "TID-ABC123DEF4567890",
"status": "expired",
"amount": "100"
}
| Campo | Descrição |
|---|---|
merchant_id | Código do comerciante da loja. |
request_id | Seu ID de pedido (se fornecido ao criar a fatura). |
trans_id | Código da fatura. Use para atualizar o pedido correspondente. |
status | Sempre "expired". |
amount | Valor em USD (string). |
Você pode usar esta informação para atualizar o pedido como "expirado", cancelar reservas, etc. Você ainda deve retornar HTTP 2xx para que o sistema não trate como erro.
3. Exemplo de Handler de Webhook (Node.js)
app.post('/webhook/payment', express.json(), async (req, res) => {
const { merchant_id, api_key, trans_id, request_id, status, amount, transaction_hash } = req.body;
// Verificar merchant_id (completed e expired)
if (merchant_id !== process.env.PAYERSCAN_MERCHANT_ID) {
return res.status(401).send('Unauthorized');
}
// Verificar api_key (apenas completed — expired não inclui api_key)
if (status === 'completed' && api_key !== process.env.PAYERSCAN_API_KEY) {
return res.status(401).send('Unauthorized');
}
// Encontrar pedido por request_id ou trans_id
const order = await orderService.findByRequestId(request_id) || await orderService.findByTransId(trans_id);
if (!order) {
return res.status(404).send('Order not found');
}
if (status === 'completed') {
await orderService.markPaid(order, { transaction_hash, amount });
} else if (status === 'expired') {
await orderService.markExpired(order);
}
res.status(200).send('OK');
});
Verificação de Assinatura
Para garantir que os webhooks realmente vêm do PayerScan, você deve verificar a assinatura ou fazer a verificação cruzada dos dados.
- HTTPS: Sempre use HTTPS para
callback_urlpara evitar interceptação. - Verificação da API Key (apenas completed): O webhook
completedinclui suaapi_keyno payload. Compare-a com sua API key armazenada para verificar a autenticidade. Nota: o webhookexpirednão incluiapi_key.
Idempotência
Um trans_id pode receber apenas um evento completed, mas as retentativas podem reenviá-lo. Garanta que sua lógica de processamento seja idempotente (atualizar múltiplas vezes é seguro, sem crédito duplicado).
Retentativa em caso de falha do Webhook
Quando o servidor do Merchant retorna um erro (4xx/5xx) ou timeout, o sistema retenta o webhook seguindo este cronograma:
| Tentativa | Momento |
|---|---|
| Tentativa 1 | Imediatamente (quando a fatura é concluída) |
| Tentativa 2 | Após 10 segundos |
| Tentativa 3 | Após 30 segundos |
| Tentativa 4 | Após 30 segundos |
| Tentativa 5 | Após 60 segundos |
Máximo de 5 tentativas. Após 5 falhas, callback_status muda para failed e nenhuma tentativa adicional é feita.
Os comerciantes devem garantir que seu endpoint seja estável e retorne 2xx rapidamente (< 10 segundos) para evitar retentativas desnecessárias.