Skip to content

Webhooks

BD Voucher receives webhooks from payment and voucher providers, and forwards a normalized set of events to your configured receiver URLs.

Payment webhooks

These are the inbound endpoints the gateways call back into BD Voucher. We always re-verify each one server-side before marking a payment SUCCEEDED. See the Payments guide for the full flow.

SourcePathVerbVerification used
SSLCommerz/api/v1/payments/sslcommerz/callbackPOSTvalidate / transactionQueryByTransactionId
bKash/api/v1/payments/bkash/callbackGET/POSTexecutePayment (transactionStatus === "Completed")
PayStation/api/v1/payments/paystation/callbackGET/POST/transaction-status (trx_status === "successful")
Zendit/api/v1/zendit/webhookPOSTHMAC signature

Each of the three payment endpoints is idempotent — a payment that’s already SUCCEEDED is a no-op. Concurrent callbacks for the same order are serialized via a Redis lock keyed by order_id so we never double-fulfill.

Signature verification

All outbound webhooks are signed with HMAC-SHA256:

X-BDV-Signature: sha256=hex(hmac_sha256(secret, rawBody))
X-BDV-Timestamp: 1713648392

Verify in Node.js:

import crypto from 'node:crypto';
function verify(rawBody, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}

Retries

Failed deliveries (non-2xx) are retried with exponential backoff:

AttemptDelay
1immediate
230 s
32 min
410 min
51 h

After five failures the event is moved to the dead-letter queue; operators can replay via the admin API.

Idempotency

Every event carries a stable eventId. Your handler should be idempotent — store processed eventIds and short-circuit duplicates.