# Webhook баптау

> Кабинетте webhook адресін қосу, оқиғаларды таңдау, құпияны сақтау, тақырыптар мен дене пішімі, 11 рет қайталау кестесі, журналды оқу, адресті сынау және қайта бағыттау ережесі.

## Қысқаша

Webhook — счёттың күйі өзгергенде біз сіздің серверіңізге жіберетін HTTP сұрау. Оны қосу үшін кабинеттің **Интеграциялар** бөліміне барып, өз адресіңізді қосасыз: https://qut.kz/app

Продакшенде адрес **тек `https`** және **нақты домен** болуы керек. IP адрес, `localhost` және уақытша туннель адрестері қабылданбайды. Құпия (secret) **бір рет** көрсетіледі — сол сәтте көшіріп алыңыз.

Адресіңіз авторизациясыз ашық болуы тиіс: біз қарапайым сыртқы клиент ретінде келеміз, логин мен пароль жібермейміз.

## Адрес қосу

1. Кабинетке кіріңіз: https://qut.kz/app
2. **Интеграциялар** бөліміне өтіңіз.
3. Webhook адресін қосыңыз — толық жол, мысалы `https://site.kz/qutpay-webhook`.
4. Керекті оқиғаларды белгілеңіз.
5. Көрсетілген құпияны көшіріп, серверіңіздің құпия айнымалысына салыңыз (`QUTPAY_WEBHOOK_SECRET` сияқты).
6. Сынау батырмасымен тексеріп көріңіз.

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

## Адреске қойылатын талаптар

| Талап | Продакшен | Sandbox |
|---|---|---|
| Хаттама | Тек `https` | `http` да болады |
| Домен | Нақты домен керек | `localhost` да болады |
| IP адрес | Қабылданбайды | Болады |
| Туннель адресі | Қабылданбайды | Болады |
| Авторизация | Болмауы керек | Болмауы керек |

Талапқа сай келмесе, адресті сақтағанда бірден қате көресіз:

| Код | Не болды |
|---|---|
| `invalid_url` | Адрес дұрыс емес |
| `webhook_url_requires_https` | HTTP адрес қабылданбайды |
| `webhook_url_requires_domain` | IP немесе домені жоқ адрес |
| `webhook_url_tunnel_forbidden` | Уақытша туннель адресі |
| `too_many_endpoints` | Webhook саны шектен асты |

## Оқиғаларды таңдау

Барлық оқиғаны қосудың қажеті жоқ. Көбіне екеуі жеткілікті: `invoice.paid` және `invoice.refunded`.

| Топ | Оқиғалар |
|---|---|
| Счёт | `invoice.created`, `invoice.pending`, `invoice.paid`, `invoice.cancelled`, `invoice.expired`, `invoice.failed`, `invoice.lost` |
| Қайтару | `invoice.refunded`, `invoice.partially_refunded`, `refund.done`, `refund.failed`, `refund.unknown` |
| Жалпы | `invoice.status` — кез келген күй ауысуына бір арна |
| Қателер | `invoice.create_failed`, `invoice.cancel_failed`, `invoice.bulk` |
| Жазылым | `subscription.created`, `subscription.status` |

Қай оқиға қай сәтте келетіні: [Счёттың өмірлік циклі](/kb/invoice-lifecycle).

## Тақырыптар

Әр сұрауда төрт тақырып келеді:

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

Қолтаңбаны міндетті түрде тексеріңіз: [Webhook қауіпсіздігі](/kb/webhook-security).

## Дене мысалы

```
POST /qutpay-webhook HTTP/1.1
Host: site.kz
Content-Type: application/json
X-Webhook-Event: invoice.paid
X-Webhook-Timestamp: 1757167000
X-Webhook-Signature: sha256=3f1a…
X-Webhook-Delivery: 17

{
  "event": "invoice.paid",
  "invoice": {
    "id": "inv_…",
    "externalOrderId": "1001",
    "status": "paid",
    "amount": 2500,
    "metadata": { "branch": "almaty-1" },
    "receiptUrl": "…",
    "paidAt": "…"
  },
  "sentAt": "…"
}
```

`externalOrderId` пен `metadata` — сіз счёт жасағанда берген мәндер, олар өзгертілмей қайтады.

## Қайталау кестесі

Серверіңіз **2xx емес** жауап берсе немесе мүлдем жауап бермесе, біз сұрауды **11 рет қайталаймыз**. Аралық 10 секундтан басталып, бір сағатқа дейін өседі.

Бұдан екі қорытынды шығады:

1. **Алдымен 200 қайтарыңыз, жұмысты содан кейін істеңіз.** Ұзақ өңдеу таймаутқа әкеледі, ал таймаут — қайталау.
2. **Өңдеуіңіз идемпотентті болсын.** Бір счёт бойынша бір күй бірнеше рет келуі мүмкін. `(invoice.id, status)` жұбы бойынша бір рет орындаңыз: [Идемпоттылық](/kb/idempotency).

```js
app.post('/qutpay-webhook', express.raw({ type: '*/*' }), async (req, res) => {
  if (!verify(req)) return res.sendStatus(401);
  res.sendStatus(200);              // бірінші жауап
  await enqueue(JSON.parse(req.body)); // сосын жұмыс
});
```

## Журнал

Кабинеттің **Интеграциялар** бөлімінде әр жіберілім жазылады: оқиға түрі, уақыты, серверіңіздің HTTP жауабы. Бір нәрсе келмей жатса, диагностика осыдан басталады.

| Журналда не тұр | Мағынасы |
|---|---|
| Бірде-бір жазба жоқ | Біз жібермедік: адрес қосылмаған, оқиға таңдалмаған немесе күй әлі өзгермеген |
| «Жіберілді», жауап 401/403 | Адрес авторизациямен жабық |
| Жауап 404 | Жол дұрыс емес немесе маршрут тіркелмеген |
| Жауап 301/302 | Қайта бағыттау — біз оны ұстамаймыз |
| Жауап 500 | Сіздің өңдеушіңіз құлап жатыр |
| Таймаут | Өңдеу тым ұзақ |

Толық диагностика: [Webhook келмей жатыр](/kb/webhook-not-arriving).

## Қайта бағыттау ережесі

307 және 308 қайта бағыттауларын біз **тек сол адрестің өзіне** ұстаймыз: `http` → `https` және қиғаш сызық қосылған нұсқа. Басқа жерге апаратын қайта бағыттауды ұстамаймыз.

Себебі қарапайым: қайта бағыттауды соқыр ұстау сұрауды бөтен адреске жіберіп қоюы мүмкін. Сондықтан кабинетке **соңғы** адресті жазыңыз, аралық адресті емес.

## Сынау

1. Кабинеттегі сынау батырмасымен сынама сұрау жіберіңіз — қолтаңбаны тексеретін кодыңыз жұмыс істеп тұрғанын осыдан көресіз.
2. Sandbox-қа ауысып, счёт жасаңыз да, `POST /api/v1/invoices/{id}/simulate {"status":"paid"}` арқылы төлемді симуляциялаңыз. Webhook шынайы жіберіледі.
3. Қайталауды сынау үшін өңдеушіңізді әдейі 500 қайтаратын етіп қойып, журналда қайталаудың келгенін қараңыз.

Толық сценарий: [Интеграцияны қалай сынау керек](/kb/testing-integration).

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

**Бірнеше адрес қосуға бола ма?** Иә. Мысалы бір адрес сайтқа, екіншісі ішкі мониторингке. Әрқайсысының өз оқиға тізімі болады.

**Webhook жалғыз жеткілікті ме?** Көп жағдайда иә. Клиент құрылғының алдында тұрып нәтиже күтетін болса, қатар күйді де сұраңыз: [Webhook пен күйді сұрау](/kb/polling-vs-webhook).

**Құпияны жоғалттым.** Кабинеттен жаңасын жасаңыз және серверіңіздегі мәнді сол сәтте ауыстырыңыз.

**Адресімді өзгерттім, ескі оқиғалар не болады?** Қайталау кезегінде тұрған оқиғалар жаңа адреске жіберіледі.

**Sandbox-та да webhook келе ме?** Иә, шынайы жіберіледі. Айырмашылығы — адрес `http` және `localhost` болуына рұқсат.
