Client libraries
Node.js / TypeScript
Integrate ShadhinPay from a Node.js backend today, and track the official SDK.
Official SDK — Planned
A first-party @shadhinpay/node package is on the roadmap.
Until it ships, use the built-in fetch and node:crypto as shown here — it's a
few lines.
Create a payment
Node 18+ has fetch built in. Keep your API key on the server, never in the browser.
const BASE = 'https://api.shadhinpay.pay/api/v1'
async function createPayment() {
const res = await fetch(`${BASE}/payments`, {
method: 'POST',
headers: {
'Client-Id': process.env.SHADHINPAY_CLIENT_ID!,
'Business-Id': process.env.SHADHINPAY_BUSINESS_ID!,
'X-Api-Key': process.env.SHADHINPAY_API_KEY!,
'X-Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: '500.00',
currency: 'BDT',
merchantTxnId: 'order-7831',
callbackUrl: 'https://shop.example.com/return',
}),
})
const body = await res.json()
if (body.status !== 'success') throw new Error(body.errorType) // see /docs/developers/errors
return body.data // { paymentId, paymentUrl, status, ... }
}Redirect the customer to data.paymentUrl. See Payments.
Verify a webhook (Express)
Capture the raw body so the signature matches the signed bytes:
import express from 'express'
import crypto from 'node:crypto'
const app = express()
app.post(
'/webhooks/shadhinpay',
express.raw({ type: 'application/json' }), // raw bytes, not parsed JSON
(req, res) => {
const raw = req.body.toString('utf8')
const header = req.header('x-shadhinpay-signature') ?? ''
if (!verify(raw, header, process.env.SHADHINPAY_WEBHOOK_SECRET!)) {
return res.status(400).end()
}
const event = JSON.parse(raw)
// dedupe on event.eventId, then handle event.eventType
res.status(200).json({ status: 'received' })
},
)
function verify(raw: string, header: string, secret: string): boolean {
const p = Object.fromEntries(header.split(',').map((kv) => kv.split('=')))
const t = Number(p.t)
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false
const expected = crypto.createHmac('sha256', secret).update(`t=${t}.${raw}`).digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(p.v1 ?? '')
return a.length === b.length && crypto.timingSafeEqual(a, b)
}The full signing scheme, retries, and idempotency rules are in Webhooks.