Вебхуки

Обработка webhook

После каждого завершённого платежа Finik отправляет POST на ваш webhookUrl. Проверьте его, затем обновите заказ.

Пример payload

POST /webhooks/finik
{
  "id": "transaction-id-15423_DEBIT", // ID операции в Finik (суффикс _DEBIT — списание средств)
  "transactionId": "transaction-id-241234", // ID транзакции платежа — используйте для идемпотентности (дедупликации)
  "status": "success", // статус платежа; может прийти в любом регистре — проверяйте регистронезависимо
  "amount": 100, // сумма платежа
  "fields": { // поля платёжной формы и ваши additionalData
    "amount": 100, // сумма, зафиксированная на форме
    "paymentId": "payment-id-241234", // ID платежа, который вы передали в POST /v1/payment
    "fieldId1": "value1", // значение вашего additionalData-поля с fieldId "fieldId1"
    "fieldId2": "value2" // значение вашего additionalData-поля с fieldId "fieldId2"
  },
  "requestDate": 1737369012345, // время создания запроса (UNIX, мс)
  "transactionDate": 1737369012345, // время проведения транзакции (UNIX, мс)
  "service": { "id": "VISA" } // приходит, только если у вас подключён приём по картам Visa
}

Что делать

  1. Проверьте подпись — Постройте каноническую строку из входящего запроса (метод, путь, host + все заголовки x-api-*, query, тело), затем проверьте заголовок signature по публичному ключу Finik для вашей среды.
  2. Обеспечьте идемпотентность — Используйте transactionId (или id) для дедупликации. Finik может доставить один webhook несколько раз (доставка «хотя бы раз»).
  3. Отвечайте быстро — Верните 200 OK сразу и выполняйте тяжёлую работу асинхронно.

Повторные попытки доставки

Webhook доставляется по принципу «хотя бы один раз». Если ваш сервер не ответил 200 OK (или ответил с ошибкой), Finik повторяет отправку.

  • Первые 5 попыток — каждые 30 секунд.
  • Дальше — интервал удваивается (экспоненциальная задержка): начинается с 60 секунд и ограничен сверху 24 часами (86 400 секунд).
ПопыткаЗадержка
160 сек (1 мин)
2120 сек (2 мин)
3240 сек (4 мин)
4480 сек (8 мин)
5960 сек (16 мин)
61920 сек (32 мин)
73840 сек (1 ч 4 мин)
87680 сек (2 ч 8 мин)
915360 сек (4 ч 16 мин)
1030720 сек (8 ч 32 мин)
1161440 сек (17 ч 4 мин)
12122880 сек → ограничено до 86400 сек (24 ч)

Достигнув 24 часов, интервал больше не растёт — все последующие попытки выполняются раз в сутки.

Как правильно проверить подпись

Ниже примеры, как правильно проверить подпись.

Установите @mancho.devs/authorizer и node-fetch, затем:

check-signature.ts
import { Signer } from "@mancho.devs/authorizer";
import * as dotenv from "dotenv";
dotenv.config({ path: ".env.development" });

const publicKey = process.env.FINIK_BETA_PUBLIC_KEY;

const requestData = {
  body: {
    fields: {
      amount: 1,
      webhook_url: "https://huntpro.kg/finik-callback",
      paymentId: "141fc797-0366-480e-8ba7-2982632b2b2c",
      success_redirect_url:
        "https://api.acquiring.averspay.kg/v1/redirect?paymentId=141fc797-0366-480e-8ba7-2982632b2b2c&clientId=1295102413&status=succeeded",
      name: "HuntPro",
      qrTransactionId: "aaa76e79-f808-4ac5-b88e-76a9b4055832",
    },
    amount: 1,
    transactionDate: 1781697937466,
    clientId: "25c1aabe-fc4a-4968-85d9-90600466e9a5",
    id: "320259584_aaa76e79-f808-4ac5-b88e-76a9b4055832_DEBIT",
    status: "succeeded",
    transactionId: "141fc797-0366-480e-8ba7-2982632b2b2c",
    data: {
      accountId: "8d4e1d11-e50c-4278-a5e2-4e55de196c6a",
      description: "Order 2606",
      webhookUrl: "https://huntpro.kg/finik-callback",
      name_en: "HuntPro",
    },
  },
  httpMethod: "POST",
  headers: {
    "Content-Type": "application/json",
    Host: "huntpro.kg",
    "x-api-timestamp": "1781763261255",
  },
  path: "/finik-callback",
  queryStringParameters: null,
};

const signature = "jS1a8HW4BTIea8iCmoW/EF7FXUA0JSsHe/kSRBfCYdxFwvyrMe0SOfU9pAKzOOO0bIsa9FW+WqAzqxExHjcscmG8k/vwgB+dCMcM04K91vTL6eZAVOE/Hcx2iALNZnG6UVnP/jnUt8MjK+xwBhamwFktxdUovf+cVFZ2io10RAT1pOEIHtZceXbQeg7XlhcPuTAKmOuF1B4gGoksRtGglGwwyvYaa9ml8OPMP3uoCaauYqT0aUKLTNsJdFfO0Q3Dvsy9uyWvuVa5t6adbIm+SHlbz9VGSZ2xrwgETUBVj86dfA3oiAgERLKhQTBSMTxRYlUKhPVHGfR3kjAhD3g4JQ==";

async function checkSignature() {
  const isValid = await new Signer(requestData as any).verify(
    publicKey!,
    signature,
  );

  console.log("Is valid signature:", isValid);
}

checkSignature();