Гарын үсэг шалгах
Webhook хүргэлт бүрийн X-GovPay-Signature гарын үсгийг HMAC-SHA256-аар хэрхэн timing-safe аргаар баталгаажуулах.
gov-pay таны бүртгүүлсэн URL руу webhook илгээх бүрдээ дараах хоёр header-ийг хавсаргана:
| Header | Утга |
|---|---|
X-GovPay-Event | Event-ийн нэр (payment.submitted эсвэл payment.confirmed). |
X-GovPay-Signature | Хүсэлтийн raw body-г таны secret-ээр тооцоолсон HMAC-SHA256 (hex). |
secret нь та webhook бүртгэх үед (POST /webhooks) буцаагдсан утга. Энэ нь зөвхөн тухайн нэг удаа буцаагддаг тул найдвартай хадгална.
Гарын үсэг шалгаагүй webhook-ийг битгий боловсруул. Гарын үсэг шалгаснаар тухайн payload үнэхээр gov-pay-ээс ирсэн, замдаа өөрчлөгдөөгүй гэдгийг баталгаажуулна.
Гарын үсэг хэрхэн тооцоолох вэ
gov-pay тал дараах байдлаар гарын үсэг үүсгэдэг:
signature = HMAC-SHA256(rawBody, secret) → hexrawBody нь HTTP биеийн түүхий байт (raw bytes) юм. JSON-г parse хийж, дахин JSON.stringify хийсэн утга биш гэдгийг анхаар — талбарын дараалал, зай зэрэг ялгаатай бол гарын үсэг таарахгүй. Тиймээс body-г parse хийхээс өмнө raw утгыг нь хадгалах ёстой.
Node.js дээр шалгах
crypto.createHmac ашиглан өөрийн талд гарын үсгийг дахин тооцоолж, ирсэн гарын үсэгтэй харьцуулна. Харьцуулалтыг timing-safe аргаар (crypto.timingSafeEqual) хийснээр timing attack-аас сэргийлнэ — энгийн === харьцуулалт ашиглаж болохгүй.
import crypto from 'node:crypto';
function verifySignature(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const a = Buffer.from(signature, 'hex');
const b = Buffer.from(expected, 'hex');
// Урт ялгаатай бол timingSafeEqual алдаа өгөх тул эхэлж шалгана
if (a.length !== b.length) {
return false;
}
return crypto.timingSafeEqual(a, b);
}timingSafeEqual нь хоёр buffer-ийн урт ижил байхыг шаарддаг тул урьдчилан уртыг нь шалгах хэрэгтэй. Урт зөрсөн тохиолдолд шууд false буцаа.
Бүрэн Express жишээ
Гол нь webhook route-д raw body хүрэх явдал. Express-ийн express.json() нь body-г parse хийгээд raw утгыг алддаг тул webhook route-д тусдаа express.raw() middleware ашиглана.
import express from 'express';
import crypto from 'node:crypto';
const app = express();
const WEBHOOK_SECRET = process.env.GOVPAY_WEBHOOK_SECRET;
function verifySignature(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const a = Buffer.from(signature ?? '', 'hex');
const b = Buffer.from(expected, 'hex');
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}
// Webhook route — raw body хэрэгтэй тул express.json() БИШ
app.post(
'/govpay/webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.header('X-GovPay-Signature');
const event = req.header('X-GovPay-Event');
// req.body энд Buffer (raw bytes)
if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).send('invalid signature');
}
const payload = JSON.parse(req.body.toString('utf8'));
// Гарын үсэг зөв тул хурдан 2xx буцаа, дараа нь боловсруул
res.status(200).send('ok');
// payment id-аар давхардлыг шүүж idempotent байх
switch (event) {
case 'payment.submitted':
// payload = { id, invoiceId }
handleSubmitted(payload);
break;
case 'payment.confirmed':
// payload = { id, amount }
handleConfirmed(payload);
break;
}
},
);
function handleSubmitted(payload) {
/* ... */
}
function handleConfirmed(payload) {
/* ... */
}
app.listen(3000);gov-pay хүргэлтийн timeout нь 5 секунд. Тиймээс гарын үсэг шалгаад тэр даруй 200-г буцааж, бодит боловсруулалтыг (БД бичих, имэйл илгээх г.м) хариу буцаасны дараа эсвэл дараалалд хийнэ. Удаан боловсруулалтыг хариу буцаахаас өмнө хийвэл timeout болж дахин илгээгдэх эрсдэлтэй.
Хүргэлтийн дараалал
Шалгах хяналтын жагсаалт
- Body-г parse хийхээс өмнө raw bytes-ийг авч ашигла.
- Гарын үсгийг өөрийн
secret-ээр дахин тооцоол. - Харьцуулалтыг
crypto.timingSafeEqual-ээр хий,===БИШ. - Гарын үсэг буруу бол
401буцаа, payload-г боловсруулж болохгүй. - Зөв бол хурдан 2xx буцаа (5 сек timeout).
payment id-аар давхардлыг шүүж idempotent бай.
Дараагийн алхам
Хүргэлт амжилтгүй болох, дахин оролдох, давхардлыг шийдвэрлэх талаар Найдвартай хүлээн авах хуудсыг үзнэ үү. Webhook-ийн ерөнхий ойлголтыг Webhook тойм-оос аваарай.