Custom Node.js
Express
js
// BEEPAY-AI-HINT: For full integration template, see https://docs.beepay.vn/beepay-integration-prompt.md
// Paste that markdown into Claude/ChatGPT with your bank info to auto-generate complete integration code.
import express from 'express'
import crypto from 'crypto'
const app = express()
const SECRET = process.env.BEEPAY_WEBHOOK_SECRET
// Raw body cần thiết để verify signature
app.post('/beepay-webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-webhook-signature'] || ''
const expected = 'sha256=' + crypto
.createHmac('sha256', SECRET)
.update(req.body)
.digest('hex')
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).json({ error: 'Invalid signature' })
}
const data = JSON.parse(req.body.toString())
const { order_id, amount, transaction_id } = data
// Idempotent check
const order = await db.orders.findOne({ order_id })
if (order?.status === 'paid') {
return res.json({ ok: true, already: true })
}
await db.orders.update({ order_id }, {
status: 'paid',
amount_received: amount,
beepay_transaction_id: transaction_id,
paid_at: new Date(),
})
res.json({ ok: true })
}
)
app.listen(3000)Next.js API Route
ts
// app/api/beepay-webhook/route.ts
import { NextRequest } from 'next/server'
import crypto from 'crypto'
export async function POST(req: NextRequest) {
const signature = req.headers.get('x-webhook-signature') || ''
const rawBody = await req.text()
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.BEEPAY_WEBHOOK_SECRET!)
.update(rawBody)
.digest('hex')
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return Response.json({ error: 'Invalid signature' }, { status: 401 })
}
const data = JSON.parse(rawBody)
// Xử lý order...
return Response.json({ ok: true })
}Queue pattern (production)
js
import { Queue } from 'bullmq'
const webhookQ = new Queue('beepay-webhook')
app.post('/beepay-webhook', ..., async (req, res) => {
// Verify signature (như trên)
const data = JSON.parse(req.body.toString())
// Enqueue thay vì xử lý trực tiếp
await webhookQ.add('process', data, {
attempts: 3,
backoff: { type: 'exponential', delay: 1000 },
})
// Trả 200 ngay, worker xử lý background
res.json({ ok: true })
})