Qut Pay Сайт Кабинет Білім базасы Нұсқаулықтар API құжаттамасы ҚАЗРУС
Басты бетБілім базасы → Мәселені шешу

Webhook қолтаңбасы сәйкес келмейді

Жаңартылды: 2026-09-14 · Markdown нұсқасы

Қысқаша

Қолтаңба былай есептеледі: HMAC-SHA256(secret, timestamp + "." + rawBody), нәтижесі hex түрінде, sha256= префиксімен X-Webhook-Signature тақырыбында келеді. Сәйкес келмеуінің ең жиі себебі біреу: сіз денені JSON-ға айналдырып, сосын қайта жолға түрлендіресіз. Бұл кезде байттар өзгереді — бос орындар, өрістердің реті, Unicode таңбалары — сондықтан қолтаңба ешқашан сәйкес келмейді.

Шешім: денені өзгертілмеген байт күйінде алып, сол күйінде тексеру. Ал JSON-ға айналдыруды тексеруден кейін жасау.

Дұрыс есептеу

import crypto from 'node:crypto';

function verify(secret, timestamp, rawBody, signature) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(timestamp + '.' + rawBody)  // rawBody — Buffer немесе өзгермеген жол
    .digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(signature || '');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Салыстыруды === арқылы емес, timingSafeEqual арқылы жасаңыз.

Тақырыптар:

ТақырыпНе келеді
X-Webhook-EventОқиға атауы, мысалы invoice.paid
X-Webhook-TimestampҚолтаңбаға кіретін уақыт белгісі
X-Webhook-Signaturesha256= префиксі бар hex қолтаңба
X-Webhook-DeliveryЖеткізу идентификаторы, журналға жазуға ыңғайлы

Симптом бойынша себеп

СимптомСебебіШешімі
Ешқашан сәйкес келмейдіДене JSON-ға айналдырылып, қайта жолға түрлендірілгенRaw body алыңыз
Кейде сәйкес келеді, кейде жоқДенеде кириллица немесе эмодзи бар, қайта түрлендіру байтты өзгертедіRaw body алыңыз
Барлық жерде сәйкес, тек продакшенде емесҚұпия орталары бойынша бөлек, .env жаңартылмағанСол орта үшін жасалған құпияны қойыңыз
sha256= префиксі артық болып тұрСалыстыруда префикс есепке алынбағанКүтілетін мәнге де sha256= қосыңыз
Timestamp ескі деп қабылданбайдыСервер сағаты ауытқыған немесе оқиға кезекте ұзақ тұрғанСағатты NTP-ге қосыңыз
Қолтаңба API кілтпен есептелгенҚұпия мен API кілт шатастырылғанWebhook құпиясын қолданыңыз

Ең жиі қате: денені қайта құрастыру

Көп фреймворк келген JSON-ды автоматты түрде объектіге айналдырады. Кейін сіз сол объектіні қайта жолға түрлендірсеңіз, нәтиже түпнұсқаға байт-байт сәйкес келмейді:

// ДҰРЫС ЕМЕС — қолтаңба ешқашан сәйкес келмейді
const raw = JSON.stringify(req.body);

Неге:

HMAC — байттардың функциясы. Бір ғана байт өзгерсе, қолтаңба мүлде басқа болады.

Raw body қалай алынады

Express. Қолтаңбаны тексеретін маршрутқа express.json() орнына express.raw() қойыңыз:

app.post('/webhooks/qutpay',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const raw = req.body;                     // Buffer, өзгермеген
    const ts  = req.headers['x-webhook-timestamp'];
    const sig = req.headers['x-webhook-signature'];

    if (!verify(process.env.WEBHOOK_SECRET, ts, raw, sig)) {
      return res.sendStatus(400);
    }

    const event = JSON.parse(raw.toString('utf8')); // тексергеннен КЕЙІН
    res.sendStatus(200);
  });

Маңызды: express.json() бүкіл қосымшаға app.use(express.json()) арқылы қосылып тұрса, ол бұл маршрутқа дейін жетіп үлгереді. Webhook маршрутын жалпы express.json() шақырылғанға дейін тіркеңіз.

Laravel.

$raw = $request->getContent();  // өзгермеген дене
$expected = 'sha256=' . hash_hmac(
    'sha256',
    $request->header('X-Webhook-Timestamp') . '.' . $raw,
    config('services.qutpay.webhook_secret')
);

if (!hash_equals($expected, $request->header('X-Webhook-Signature', ''))) {
    abort(400);
}

$event = json_decode($raw, true);

$request->all() емес, дәл $request->getContent() қолданыңыз.

Django.

import hmac, hashlib

raw = request.body                      # bytes, өзгермеген
ts  = request.headers.get('X-Webhook-Timestamp', '')
sig = request.headers.get('X-Webhook-Signature', '')

expected = 'sha256=' + hmac.new(
    secret.encode(),
    ts.encode() + b'.' + raw,
    hashlib.sha256,
).hexdigest()

if not hmac.compare_digest(expected, sig):
    return HttpResponseBadRequest()

request.POST немесе request.data емес, request.body керек. DRF қолдансаңыз, request.body-ға request.data оқылғанға дейін қол жеткізіңіз.

Таза Node.

const chunks = [];
req.on('data', (c) => chunks.push(c));
req.on('end', () => {
  const raw = Buffer.concat(chunks);
  // тексеру осында
});

Timestamp тексеру

Қолтаңба дұрыс болса да, ескі сұрауды қабылдамаған жөн. X-Webhook-Timestamp мәні 5 минуттан ескі болмауы керек:

const age = Math.abs(Date.now() / 1000 - Number(ts));
if (age > 300) return res.sendStatus(400);

Бұл қайталап жіберу шабуылынан қорғайды. Егер бұл тексеру жалған түрде істеп жатса, серверіңіздің сағатын тексеріңіз: NTP қосулы болуы керек.

Құпияны шатастырмаңыз

Екі түрлі құпия бар және олар бір-біріне мүлде ұқсамайды:

НеҚайда қолданыладыПішімі
API кілтСұрау жіберуде, X-API-Key тақырыбындаqp_live_… / qp_test_…
Webhook құпиясыТек келген webhook қолтаңбасын тексерудеБөлек мән, qp_ деп басталмайды

Webhook құпиясы кабинетте бір рет қана көрсетіледі — webhook адресін қосқан сәтте. Жоғалтып алсаңыз, қайта қарауға болмайды: жаңасын жасап, кодыңызды жаңартасыз.

Егер X-API-Key тақырыбымен мәселе болса, ол мүлде басқа әңгіме: API 401 қайтарады.

Тексеру реті

  1. Келген сұраудың дәл байттарын журналға жазыңыз (hex немесе base64 түрінде)
  2. Сол байттардан қолтаңбаны қолмен есептеп, тақырыптағы мәнмен салыстырыңыз
  3. Сәйкес келсе — мәселе кодыңыздағы raw body алуда
  4. Сәйкес келмесе — құпия басқа немесе timestamp дұрыс қосылмаған
  5. timestamp + "." + rawBody тәртібін тексеріңіз: нүкте бөлгіш ретінде тұр
  6. Бәрі дұрыс көрінсе: Мәселе менде ме, Kaspi-де ме

Жиі қойылатын сұрақтар

Қолтаңба сәйкес келмесе, қандай жауап беру керек? 400. Сонда оқиға қайталанады — бұл дұрыс: кодыңызды түзетіп үлгерсеңіз, оқиға жоғалмайды.

Тексермеуге бола ма? Болмайды. Webhook адресіңіз авторизациясыз ашық тұрады, яғни оған кез келген адам сұрау жібере алады. Қолтаңба — сұраудың бізден келгенінің жалғыз дәлелі.

Құпияны ауыстырсам, ескі webhook-тар не болады? Ауыстырған сәттен бастап жаңа құпия қолданылады. Кодты бір уақытта жаңартыңыз.

Бір адресте бірнеше құпия бола ма? Әр webhook адресінің өз құпиясы болады. Бірнеше адрес қоссаңыз, әрқайсысын бөлек сақтаңыз.

Прокси артындағы серверде істей ме? Иә, бірақ прокси денені өзгертпейтініне көз жеткізіңіз. Кейбір баптаулар JSON-ды қайта пішімдейді — ондайда қолтаңба ешқашан сәйкес келмейді.

Байланысты мақалалар

Кеш келген төлем — счёт жабылған, ал ақша келдіБолдырылған немесе мерзімі өткен счётқа ақша кешігіп келуі мүмкін. Ондайда invoice.paid оқиғасы late: true белгісімен келеді. Не істеу керек және кодта бұған қалай дайын болу керек.Мәселе менде ме, Kaspi-де ме — екі минуттық диагностикаҮш сұраққа жауап беріп, ақаудың қай жақта екенін анықтайсыз: интеграцияда, Kaspi байланысында әлде қызметте. Әр жауапқа нақты әрекет және қолдауға не жинау керегі.API 401 қайтарады — кілт қабылданбай жатыр401 unauthorized дегені — сұрауыңызда жарамды API кілт жоқ. Себептері, тексеру реті және жұмыс істейтін curl мысалы. Көбіне тақырып атауы немесе Bearer префиксі кінәлі.

Сұрағыңыз қалды ма? WhatsApp +77788813333 · kazprose@gmail.com
Кабинеттен де жазуға болады: Қолдау.

Qut Pay — тәуелсіз сервис, «Kaspi Bank» АҚ-мен аффилирленбеген. Kaspi және Kaspi Pay — құқық иесінің тауар белгілері.