Part 4 · 2 chapters · ~12 min

Rails, Webhooks and Callbacks

How payment rails behave (instant, batch, card networks), callbacks versus polling, verifying and deduplicating webhooks, acknowledging fast and processing later, out-of-order events, confirming state with the provider API, and adapters that hide each rail's quirks.

9

How rails behave

rail typeexamplesbehaviour engineers must handle
instant account-to-accountNIP (Nigeria), Pix, UPI, Faster Paymentsseconds, but timeouts and later reversals happen; name enquiry first
mobile moneyM-Pesa, MTN MoMoasynchronous callbacks; customer must approve on phone
batch / ACH-stylebulk payroll filessubmitted today, settled tomorrow; returns days later
cardsVisa, Mastercard, Verve via processorsauthorise, capture, settle in batches; chargebacks weeks later
processors / aggregatorsPaystack, Flutterwave, Stripewebhooks, their own idempotency, settlement to your bank on a schedule
10

Webhooks, done safely

code
// verify a signed webhook before trusting it (HMAC-SHA512 style, as many processors use)
import { createHmac, timingSafeEqual } from 'node:crypto';
app.post('/webhooks/processor', express.raw({ type: '*/*' }), async (req, res) => {
  const sig = Buffer.from(req.get('x-signature') ?? '', 'hex');
  const mac = createHmac('sha512', process.env.WEBHOOK_SECRET).update(req.body).digest();
  if (sig.length !== mac.length || !timingSafeEqual(sig, mac)) return res.sendStatus(401);
  const evt = JSON.parse(req.body.toString('utf8'));
  await db.query('INSERT INTO inbound_events (provider, event_id, type, payload) VALUES ($1,$2,$3,$4) ON CONFLICT DO NOTHING',
                 ['processor', evt.id, evt.event, evt]);
  res.sendStatus(200);                              // a worker processes inbound_events
});
RECEIVING A PROCESSOR WEBHOOK SAFELY
verify, dedupe, record, acknowledge fast, process later
processorwebhook endpointdatabaseworkerPOST charge.succeeded (signed)verify HMAC signature + timestamp
swipe the figure sideways, or tap expand for full screen
1/5
verify
Check the signature (HMAC over the raw body with the shared secret) and the timestamp to reject forged and replayed webhooks.
HMAC over the raw body + timestamp windowparse JSON after verifying