Webhooks
Defina callbackUrl num pagamento e a Stablezact fará POST para esse endereço à
medida que o pagamento avança. É a forma fiável de saber que um pagamento foi
liquidado, porque não depende de o cliente manter um separador do navegador
aberto.
config={{ amount: 49.99, currency: 'USDC', callbackUrl: 'https://yourstore.com/webhooks/stablezact', orderId: 'ORD-1234', metadata: { basket: 'b-99' },}}Tanto orderId como metadata são devolvidos em cada evento, e é assim que
associa um evento a uma encomenda sem manter o seu próprio mapeamento.
Eventos
Seção intitulada “Eventos”Leia type. É o mesmo vocabulário reduzido, seja qual for a forma como o cliente
pagou.
type |
Significado | Processar a encomenda |
|---|---|---|
payment.pending |
o dinheiro foi detetado, o pagamento ainda não é final | não |
payment.succeeded |
o cliente pagou | sim |
payment.underpaid |
chegou menos do que o valor faturado | não, avise o cliente |
payment.failed |
expirado, cancelado ou falhado | não |
payment.refunded |
o dinheiro foi devolvido ao cliente | anule a encomenda |
settlement.completed |
a transferência chegou à sua wallet | já processada |
settlement.delayed |
a transferência está mais lenta do que o habitual, com novas tentativas automáticas | nada a fazer |
settlement.failed |
não foi possível concluir a transferência, entramos em contacto consigo | nada a fazer |
webhook.test |
uma entrega de teste | não |
payment.succeeded é o único que significa processar a encomenda. Todos os
outros estão disponíveis, se os quiser usar.
Os eventos payment respondem a “devo enviar esta encomenda”. Os eventos
settlement respondem a “onde está o meu dinheiro” e nunca alteram a decisão de
enviar. Muitas integrações ignoram-nos e fazem a reconciliação a partir da sua
wallet.
type também é enviado no cabeçalho X-Webhook-Type, se preferir encaminhar com
base num cabeçalho em vez de analisar o corpo.
O campo event antigo
Seção intitulada “O campo event antigo”Cada payload inclui também um campo event, anterior a type. Usa duas
convenções de nomes, dá dois nomes diferentes ao mesmo resultado consoante a
forma como o cliente pagou e expõe os nossos passos internos de liquidação.
Continua a funcionar e vai continuar a funcionar, por isso as integrações
existentes não precisam de ser alteradas.
Construa as novas integrações com base em type. Se estiver a ler uma integração
antiga, esta é a correspondência entre os dois:
event |
type |
|---|---|
payment_success, deposit.confirmed |
payment.succeeded |
payment_failure, deposit.failed, bridge.failed |
payment.failed |
deposit.detected, bridge.filled |
payment.pending |
deposit.swept, settlement.completed |
settlement.completed |
settlement.delayed |
settlement.delayed |
payment.underpaid |
payment.underpaid |
payment.refunded |
payment.refunded |
webhook_test |
webhook.test |
O pagamento é paymentId em todos os payloads. Alguns eventos antigos também lhe
chamam transactionId; ambos estão presentes e são idênticos. A transação da
transferência é settlementTxHash, também presente como sweepTxHash nos
eventos antigos.
Se não tiver a certeza de que evento está a ver, obtenha o pagamento e leia
outcome. É a mesma resposta, e não depende de quais eventos lhe chegaram.
Verificar que o pedido veio de nós
Seção intitulada “Verificar que o pedido veio de nós”Cada entrega inclui dois cabeçalhos:
X-Webhook-Signature: sha256=<hex digest>X-Webhook-Timestamp: <milliseconds since epoch>Verifique antes de agir com base no corpo. Um endpoint que processa encomendas com base num POST não verificado processa encomendas para qualquer pessoa que descubra o URL.
Use o helper do SDK, se puder
Seção intitulada “Use o helper do SDK, se puder”O SDK inclui a verificação, por isso não tem de a reimplementar:
import { verifyWebhookSignature } from 'stablezact-pay';
app.post('/webhooks/stablezact', express.json(), (req, res) => { const result = verifyWebhookSignature( req.body, req.headers['x-webhook-signature'], req.headers['x-webhook-timestamp'], process.env.STABLEZACT_WEBHOOK_SECRET );
if (!result.valid) return res.status(400).json({ error: result.error });
res.sendStatus(200); handleEvent(req.body);});Existe também coinleyWebhookMiddleware, se preferir montá-lo como middleware.
Verificar por conta própria
Seção intitulada “Verificar por conta própria”import crypto from 'node:crypto';
function verify(payload, signatureHeader, timestampHeader, secret) { const ts = parseInt(timestampHeader, 10);
// Reject anything older than five minutes, so a captured delivery cannot be // replayed later. if (Date.now() - ts > 5 * 60 * 1000) return false;
const withTimestamp = { ...payload, timestamp: ts }; const serialized = JSON.stringify(withTimestamp, Object.keys(withTimestamp).sort());
const expected = crypto.createHmac('sha256', secret).update(serialized).digest('hex'); const received = signatureHeader.replace(/^sha256=/, '');
const a = Buffer.from(received, 'hex'); const b = Buffer.from(expected, 'hex'); return a.length === b.length && crypto.timingSafeEqual(a, b);}Três detalhes importantes:
O timestamp faz parte do material assinado, por isso tem de ser adicionado ao
payload antes da serialização, como número.
As chaves são ordenadas. JSON.stringify(value, Object.keys(value).sort()) é
exatamente a chamada que o servidor faz.
O seu segredo de webhook tem de ter pelo menos 32 caracteres. Segredos mais curtos são rejeitados pelo verificador, em vez de enfraquecerem a assinatura sem aviso.
Tratar as entregas
Seção intitulada “Tratar as entregas”Responda 200 rapidamente e faça o trabalho depois. Um endpoint lento parece um
endpoint que falhou.
Trate o caso de o mesmo evento chegar duas vezes. As novas tentativas e os
timeouts de rede significam entrega pelo menos uma vez (at-least-once), por isso
usar paymentId mais event como chave e ignorar as repetições compensa as
poucas linhas que exige.
