# Автоматизация в n8n: готовые workflow для Qut Pay

> Два готовых workflow для n8n — создание счёта и приём вебхуков с проверкой подписи. Импорт, credential, секрет, Raw Body и сценарий «форма → счёт → оплата → уведомление».

## Коротко

Для n8n есть **два готовых workflow**: один создаёт счёт, второй принимает события Qut Pay и проверяет их подпись. Оба собраны только из стандартных узлов (Webhook, Set, HTTP Request, Code, IF, Respond to Webhook) — ставить в n8n дополнительные пакеты не нужно.

Скачать: [api.qut.kz/downloads/qutpay-n8n.zip](https://api.qut.kz/downloads/qutpay-n8n.zip). Ссылка есть и в кабинете: **Интеграции** → список загрузок.

Понадобится: n8n 1.x (self-hosted или Cloud), API-ключ Qut Pay и секрет вебхука.

## Что внутри

| Файл | Что делает |
|---|---|
| `qutpay-create-invoice.json` | Приходит `POST /webhook/qutpay-order` → создаётся счёт в Qut Pay → в ответ `{ id, payUrl, qrUrl, deepLink, expiresAt }` |
| `qutpay-webhook-receiver.json` | Приходит `POST /webhook/qutpay-events` ← события Qut Pay; проверяется HMAC-подпись, ветвление по `invoice.paid`, ответ 200 |

Импорт: n8n → **Workflows → Add workflow → ⋯ → Import from File** → выбрать JSON. Повторить для второго файла. После импорта workflow **неактивны** — сначала настройте credential и секрет, потом включите переключатель **Active**.

## 1. Credential для API-ключа

Ключ создаётся в кабинете: **Интеграции** → API-ключи → создать. Для проверки возьмите ключ песочницы (`qp_test_…`), для продакшена — `qp_live_…`.

n8n → **Credentials → Add credential → Header Auth**:

| Поле | Значение |
|---|---|
| Credential name | `Qut Pay API key` — ровно так, узел ссылается на это имя |
| Name | `X-API-Key` |
| Value | `qp_live_…` или `qp_test_…` |

Затем откройте workflow «Qut Pay — создать счёт», узел **«Qut Pay: создать счёт»** → в поле *Credential for Header Auth* выберите созданный credential. Пока он не выбран, узел подсвечен красным.

Ключ должен оставаться внутри n8n. Не переносите его в код, который выполняется в браузере, и не встраивайте в саму форму.

## 2. Секрет вебхука

Секрет показывается **один раз** — в момент, когда вы добавляете адрес вебхука в кабинете. Узел «Проверка подписи» ищет его в таком порядке:

1. переменная окружения `QUTPAY_WEBHOOK_SECRET` (`$env`) — self-hosted;
2. n8n **Variables** с тем же именем (`$vars`) — Cloud или если доступ к `$env` закрыт.

В `.env` self-hosted n8n:

```
QUTPAY_WEBHOOK_SECRET=whsec_…
N8N_BLOCK_ENV_ACCESS_IN_NODE=false
NODE_FUNCTION_ALLOW_BUILTIN=crypto
```

После изменения переменных n8n нужно перезапустить. В n8n Cloud модуль `crypto` разрешён изначально, а секрет задаётся через **Settings → Variables**.

## 3. Регистрация адреса в кабинете

1. Включите workflow «Qut Pay — приём webhook» (**Active**).
2. Откройте узел «Webhook: события Qut Pay» и скопируйте **Production URL**, например `https://n8n.example.kz/webhook/qutpay-events`.
3. Кабинет → **Интеграции** → добавить адрес вебхука → вставьте URL → **сохраните секрет**, он показывается один раз.
4. Кнопка отправки тестового события в кабинете шлёт `webhook.test`. В n8n оно пройдёт проверку подписи, попадёт в ветку «Другие события» и получит ответ 200.

**Test URL** (`/webhook-test/…`) работает только пока нажата кнопка «Listen for test event» — для кабинета он не подходит, нужен именно Production URL.

В боевом режиме адрес должен быть `https` с реальным доменом: IP-адреса и временные туннели не принимаются.

## Как проверяется подпись

Узел «Проверка подписи» делает всю работу за вас:

- `X-Webhook-Signature` = `sha256=` + HMAC-SHA256(секрет, `timestamp + "." + rawBody`), hex;
- `X-Webhook-Timestamp` — unix-время в секундах, допуск ±300 секунд (защита от повторной отправки);
- сравнение выполняется за постоянное время;
- при несовпадении — ответ 401, и Qut Pay повторит доставку.

Одна деталь решает всё: в узле Webhook должна быть включена опция **Options → Raw Body**. Подпись считается по исходным байтам тела, а не по разобранному JSON. Подробнее: [Подпись вебхука не сходится](/kb/ru/webhook-signature-mismatch).

## Сценарий: форма → счёт → оплата → уведомление

Самая частая цепочка выглядит так:

1. Форма на сайте (Tilda, собственная вёрстка, CRM) отправляет `POST /webhook/qutpay-order` с суммой и данными клиента.
2. Узел «Поля счёта» читает поля: `amount`, `description`, `externalOrderId`, `customer.name/phone/email`, `successUrl`, `failUrl`, `metadata`. Если ваш источник присылает другие имена — поменяйте выражения в этом узле.
3. Узел «Qut Pay: создать счёт» вызывает `POST https://api.qut.kz/api/v1/invoices` и передаёт заголовок `Idempotency-Key` (по умолчанию `n8n-<externalOrderId>`). Повторный запрос с тем же номером заказа вернёт тот же счёт, а не создаст новый.
4. Ответ отдаёт `payUrl` — отправьте покупателя на эту страницу.
5. Когда клиент оплатит, Qut Pay пришлёт `invoice.paid` во второй workflow, и сработает ветка «Оплачено: ваше действие».

«Оплачено: ваше действие» — пустой узел NoOp, его заменяют своим: строка в Google Sheets, письмо, HTTP-запрос в CRM или 1С, сообщение в Telegram (пример-заглушка лежит рядом, отключён). В ветке доступны `$json.externalOrderId`, `$json.amount`, `$json.invoice.receiptUrl`, `$json.invoice.paidAt`, `$json.late`.

Чтобы счёт уходил прямо в приложение Kaspi покупателя, добавьте `kind: "phone"` в JSON-тело узла «Qut Pay: создать счёт». Тогда `customer.phone` обязателен, а сумма должна быть целой.

## Повторные события

Одно и то же событие может прийти дважды: если вы ответили не-2xx, доставка повторяется до 11 раз. Поэтому обработчик должен быть идемпотентным — ключ дедупликации это пара `(invoice.id, status)`.

Оплата может прийти и после `expired` или `cancelled` — в таком событии стоит `late: true`. Выдайте товар или верните деньги.

## Проверка в песочнице

Переведите организацию в режим песочницы и используйте ключ `qp_test_…`. Создайте счёт, затем имитируйте оплату:

```bash
curl -X POST https://api.qut.kz/api/v1/invoices/inv_…/simulate \
  -H 'X-API-Key: qp_test_…' -H 'Content-Type: application/json' \
  -d '{"status": "paid"}'
```

Через несколько секунд `invoice.paid` придёт в n8n — проверьте раздел **Executions** у workflow «Qut Pay — приём webhook». Можно и без curl: откройте `payUrl` в браузере и нажмите кнопку оплаты песочницы. Подробнее: [Симуляция оплаты в песочнице](/kb/ru/sandbox-simulate).

## Частые проблемы

| Симптом | Причина и решение |
|---|---|
| `QUTPAY_WEBHOOK_SECRET не задан` | Нет секрета в `$env`/`$vars` либо стоит `N8N_BLOCK_ENV_ACCESS_IN_NODE=true` |
| `Модуль crypto недоступен` | Добавьте `NODE_FUNCTION_ALLOW_BUILTIN=crypto` и перезапустите n8n |
| `В узле Webhook включите опцию «Raw Body»` | Включите Webhook → Options → Raw Body |
| `X-Webhook-Timestamp вне допуска` | Часы сервера n8n разошлись более чем на 5 минут — настройте NTP |
| `Подпись webhook неверна` | Секрет от другого адреса или организации, либо тело изменено прокси |
| В кабинете доставка `HTTP 404` | Workflow не активирован или указан Test URL вместо Production URL |
| `401 unauthorized` при создании счёта | Неверный ключ или credential не выбран в узле HTTP Request |
| `429 request_rate_limited` | Слишком частые запросы — соблюдайте `Retry-After` |

Если вебхук не доходит вовсе, причина обычно не в подписи: [Вебхук не приходит](/kb/ru/webhook-not-arriving).

## Вопросы и ответы

**Работает ли это в n8n Cloud?** Да. Оба workflow используют только стандартные узлы, секрет задаётся через `Settings → Variables`.

**Нужен ли отдельный community node для Qut Pay?** Нет. Узел HTTP Request работает с API напрямую, ничего доустанавливать не требуется.

**Можно ли обслуживать в одном n8n несколько организаций?** Можно, но у каждой организации свой ключ и свой секрет вебхука. Делайте отдельный credential и отдельный адрес (или отдельный workflow) на каждую.

**Как передать покупателю `payUrl` из ответа?** Перенаправьте его на этот адрес со страницы формы. Есть и готовый путь с редиректом прямо на оплату: [Tilda и любые формы](/kb/ru/tilda-forms).

**Где посмотреть коды ошибок?** Все коды собраны по группам: [Каталог ошибок](/kb/ru/error-catalog).
