Ga naar inhoud

Webhooks

Stel callbackUrl in op een betaling, en Stablezact stuurt er een POST naartoe naarmate de betaling vordert. Dit is de betrouwbare manier om te weten dat een betaling is afgewikkeld, omdat het niet afhangt van een klant die een browsertabblad open houdt.

config={{
amount: 49.99,
currency: 'USDC',
callbackUrl: 'https://yourstore.com/webhooks/stablezact',
orderId: 'ORD-1234',
metadata: { basket: 'b-99' },
}}

Zowel orderId als metadata komen bij elk event terug. Zo koppelt u een event aan een bestelling zonder zelf een koppeling bij te houden.

Lees type. Het is dezelfde kleine set waarden, hoe de klant ook heeft betaald.

type Betekenis Uitvoeren
payment.pending er is geld gezien, de betaling is nog niet definitief nee
payment.succeeded de klant heeft betaald ja
payment.underpaid er is minder aangekomen dan gefactureerd nee, wijs de klant erop
payment.failed verlopen, geannuleerd of mislukt nee
payment.refunded het geld is teruggegaan naar de klant draai de bestelling terug
settlement.completed uw uitbetaling heeft uw wallet bereikt al uitgevoerd
settlement.delayed uw uitbetaling duurt langer dan normaal, wordt automatisch opnieuw geprobeerd niets te doen
settlement.failed uw uitbetaling kon niet worden voltooid, wij nemen contact met u op niets te doen
webhook.test een testlevering nee

payment.succeeded is de enige die betekent: uitvoeren. Al het andere is er voor als u het wilt gebruiken.

De payment-events beantwoorden de vraag “moet ik deze bestelling verzenden”. De settlement-events beantwoorden de vraag “waar is mijn geld”, en ze veranderen nooit of u verzendt. Veel integraties negeren ze en reconciliëren in plaats daarvan vanuit hun wallet.

type wordt ook verzonden als de header X-Webhook-Type, als u liever op een header routeert dan de body te parsen.

Elke payload bevat ook een veld event, dat ouder is dan type. Het gebruikt twee naamgevingsconventies, geeft dezelfde uitkomst twee verschillende namen afhankelijk van hoe de klant heeft betaald, en benoemt onze interne afwikkelingsstappen. Het werkt nog steeds en blijft werken, dus bestaande integraties hoeven niet te worden aangepast.

Bouw nieuwe integraties op type. Als u een oudere integratie leest, ziet u hier hoe de twee zich tot elkaar verhouden:

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

De betaling is paymentId in elke payload. Sommige oudere events noemen die ook transactionId; beide zijn aanwezig en identiek. De uitbetalingstransactie is settlementTxHash, bij oudere events ook aanwezig als sweepTxHash.

Als u niet zeker weet naar welk event u kijkt, haal dan de betaling op en lees outcome. Dat geeft hetzelfde antwoord, en het hangt niet af van welke events u hebben bereikt.

Elke levering bevat twee headers:

X-Webhook-Signature: sha256=<hex digest>
X-Webhook-Timestamp: <milliseconds since epoch>

Verifieer voordat u iets met de body doet. Een endpoint dat bestellingen uitvoert op basis van een niet-geverifieerde POST, voert bestellingen uit voor iedereen die de URL vindt.

De SDK bevat de verificatie al, zodat u die niet opnieuw hoeft te implementeren:

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);
});

Er is ook coinleyWebhookMiddleware, als u het liever als middleware koppelt.

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);
}

Drie details die ertoe doen:

De timestamp maakt deel uit van het ondertekende materiaal, dus die moet vóór het serialiseren aan de payload worden toegevoegd, als getal.

Keys worden gesorteerd. JSON.stringify(value, Object.keys(value).sort()) is exact de aanroep die de server doet.

Uw webhooksecret moet minstens 32 tekens lang zijn. Kortere secrets worden door de verifier geweigerd, in plaats van dat de handtekening stilletjes zwakker wordt.

Antwoord snel met 200 en doe het werk daarna. Een traag endpoint lijkt op een mislukt endpoint.

Houd er rekening mee dat hetzelfde event twee keer kan binnenkomen. Nieuwe pogingen en netwerktimeouts betekenen at-least-once-levering, dus paymentId plus event als sleutel gebruiken en herhalingen negeren is de paar regels code die het kost zeker waard.