iFragment

Вебхуки

События заказов и аккаунтов, подпись HMAC-SHA256, повторы.

Вебхук это POST на URL вашего Project при каждом событии. Endpoint и секрет подписи задаются в кабинете; секрет показывается один раз.

События

typeКогда
order.succeededзаказ выполнен
order.failedзаказ не выполнен, блокировка на балансе снята
order.needs_reviewисход неизвестен, заказ проверяет поддержка
supplier_account.health_changedизменилось здоровье вашего Supplier Account (режим OWN)

Формат

POST /ifragment/webhook HTTP/1.1
Content-Type: application/json
X-IFragment-Signature: t=1790000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
{
  "id": "0199a1b2-7c3d-7e4f-8a9b-0c1d2e3f4a5b",
  "type": "order.succeeded",
  "createdAt": "2026-09-26T10:15:30.000Z",
  "data": {
    "id": "0199a1b2-6f00-7abc-9def-112233445566",
    "externalId": "1042",
    "product": "STARS",
    "quantity": 500,
    "months": null,
    "username": "durov",
    "recipientName": "Pavel Durov",
    "status": "SUCCEEDED",
    "price": "8.25",
    "currency": "USD",
    "failure": null,
    "createdAt": "2026-09-26T10:15:02.000Z",
    "sentAt": "2026-09-26T10:15:05.000Z",
    "completedAt": "2026-09-26T10:15:30.000Z"
  }
}

Для supplier_account.health_changed в data приходит аккаунт:

{
  "accountId": "0199a1b2-1111-7abc-9def-112233445566",
  "label": "Main wallet",
  "previousHealth": "HEALTHY",
  "health": "DEGRADED",
  "reasons": ["LOW_BALANCE"],
  "changedAt": "2026-09-26T10:20:00.000Z"
}
  • id это id события, он один и тот же при повторных доставках. Храните обработанные id и пропускайте дубли: доставка работает по принципу «как минимум один раз».
  • data содержит объект заказа (как в GET /v1/orders/{id}) или аккаунта для supplier_account.health_changed. У order.failed причина лежит в data.failure.code.
  • События могут прийти не по порядку; ориентируйтесь на data.status, а при сомнениях запросите заказ через API.

Ответ

Ответьте любым 2xx за 10 секунд, тело ответа не читается. Долгую обработку делайте после ответа (очередь, фоновая задача).

Повторы

При ответе не 2xx, таймауте или ошибке соединения доставка повторяется с экспоненциальной задержкой: всего 12 попыток примерно за 24 часа. После этого доставка помечается FAILED; повторить её можно вручную из кабинета.

Проверка подписи

X-IFragment-Signature: t=<unix>,v1=<hex>, где v1 = hex(HMAC-SHA256(secret, "<t>.<rawBody>")):

  1. разберите заголовок на t и v1;
  2. посчитайте HMAC-SHA256 от строки t + "." + сырое тело запроса, то есть байты как пришли, до парсинга JSON;
  3. сравните с v1 за постоянное время;
  4. отклоните запрос, если t отличается от текущего времени больше чем на 5 минут (защита от повторного воспроизведения).
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyIFragmentSignature(rawBody: string, header: string, secret: string, toleranceSec = 300): boolean {
  const parts = Object.fromEntries(header.split(',').map((part) => part.split('=', 2) as [string, string]));
  const timestamp = Number(parts.t);
  if (!parts.v1 || !Number.isInteger(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > toleranceSec) {
    return false;
  }
  const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest();
  const received = Buffer.from(parts.v1, 'hex');
  return received.length === expected.length && timingSafeEqual(received, expected);
}

// Express: берите сырое тело, а не распарсенный JSON.
app.post('/ifragment/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  if (!verifyIFragmentSignature(raw, req.get('X-IFragment-Signature') ?? '', process.env.IFRAGMENT_WEBHOOK_SECRET!)) {
    return res.sendStatus(401);
  }
  const event = JSON.parse(raw);
  res.sendStatus(200);
  queue.add(event); // обработка после ответа
});

Частая ошибка: считать подпись от заново сериализованного JSON (JSON.stringify(req.body)). Порядок полей и пробелы могут отличаться, и подпись не сойдётся. Всегда используйте сырое тело.

На этой странице