Developers
Вебхуки
Вебхук повідомляє, що замовлення змінило стан. Він не замінює читання замовлення: це сповіщення, а не джерело істини.
Опублікований контракт, ще не відкритий
Вебхуки наразі не надсилаються. Їхня форма, підпис і політика повторних спроб визначені та опубліковані тут, щоб ваш приймач можна було написати та протестувати заздалегідь.
Події
Кожна подія має однакову оболонку: id, type, timestamp і об'єкт data. Підписуйтеся на типи, які ви обробляєте, і мовчки ігноруйте решту — нові типи будуть додані, і приймач, який падає на невідомому типі, ламає сам себе.
| Тип | Що це означає |
|---|---|
order.created | Замовлення створено, курс зафіксовано. Адресу для депозиту призначено. |
order.deposit_detected | Вхідна транзакція видна в мережі до підтвердження. Використовуйте цю подію, щоб заспокоїти користувача, але ніколи не для видачі чогось. |
order.confirming | Лічильник підтверджень зростає. Випромінюється на кожному кроці, а не на кожному блоці. |
order.deposit_confirmed | Досягнуто кількості підтверджень, необхідної мережею. |
order.underpaid | Отримана сума нижча за очікувану поза межами допустимого відхилення. Замовлення не втрачено: воно чекає на вибір між поповненням, продовженням з отриманою сумою або поверненням коштів. |
order.overpaid | Отримана сума перевищує очікувану. Початкова сума залишається за зафіксованим курсом; надлишок обробляється окремо. |
order.payout_sent | Переказ відправлено через платіжну систему. Увага: відправлено не означає отримано — решта затримки залежить від банку бенефіціара. |
order.completed | Розрахунок підтверджено платіжною системою. Кінцевий стан. |
order.payout_failed | Платіжна система відхилила або повернула переказ із зазначенням причини. Автоматичний повтор не виконується при банківському поверненні: причина майже завжди в деталях. |
order.refunded | Кошти повернуто лише на адресу відправника, за вирахуванням мережевих комісій. |
Корисне навантаження
Постійна оболонка, незалежно від типу.
{
"id": "evt_01J9ZQF3K8N2M4X7",
"type": "order.payout_sent",
"created": 1788356981,
"data": {
"reference": "K7Q4-M2XB",
"state": "PAYOUT_SENT",
"railId": "sepa_instant",
"netMinor": 91228,
"currency": "EUR",
"sentAt": "2026-09-02T12:21:44Z"
}
}Підпис
Кожна доставка містить заголовок підпису: мітку часу та HMAC-SHA256, обчислений за цією міткою часу, крапкою та НЕОБРОБЛЕНИМ тілом запиту.
Fiatside-Signature: t=<timestamp unix>,v1=<hex hmac-sha256>import { createHmac, timingSafeEqual } from 'node:crypto'
// IMPORTANT : le corps doit etre le corps BRUT, avant tout parsing JSON.
// Re-serialiser l'objet change l'ordre des cles et invalide la signature.
export function verify(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=') as [string, string]))
const timestamp = Number(parts['t'])
const signature = parts['v1']
if (!timestamp || !signature) return false
// Rejeu : une signature valide capturee hier ne doit pas etre rejouable aujourd'hui.
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false
const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')
const a = Buffer.from(expected, 'hex')
const b = Buffer.from(signature, 'hex')
// Comparaison a temps constant : un === laisse fuiter la signature octet par octet.
return a.length === b.length && timingSafeEqual(a, b)
}- Підписуйте необроблене тіло до будь-якого парсингу. Повторна серіалізація JSON-об'єкта змінює порядок ключів і пробіли: підпис більше не збігатиметься, і ви довго шукатимете причину.
- Порівнюйте за постійний час. Порівняння через === зупиняється на першому відмінному байті, що дозволяє атакувальнику виміряти очікуваний підпис байт за байтом.
- Відхиляйте за межами вікна допуску в 300 секунд. Без цієї перевірки дійсний запит, захоплений учора, залишається відтворюваним сьогодні.
- Відповідайте 200 до обробки. Помістіть подію у свою чергу та обробіть її після: тривала обробка спричиняє тайм-аут і, отже, повторну спробу, навіть якщо ви отримали подію.
Повторні спроби та порядок
Будь-який статус, відмінний від 2xx, або відсутність відповіді взагалі, спричиняє повторну спробу за наведеним нижче розкладом.
| Спроба | Затримка |
|---|---|
| 1 | негайно |
| 2 | 30 s |
| 3 | 2 min |
| 4 | 10 min |
| 5 | 1 h |
| 6 | 6 h |
- Порядок прибуття не гарантується. Подія виплати може прибути до події підтвердження, яка логічно передує їй: довіряйте стану замовлення, а не порядку отримання.
- Та сама подія може надійти двічі. Зберігайте ідентифікатор події та ігноруйте вже оброблений ідентифікатор: ідемпотентність на вашому боці — це ваша відповідальність, і це легко зробити.
- Ніколи не довіряйте сумам у корисному навантаженні для зарахування чогось на вашому боці. Перечитайте замовлення через API: корисне навантаження каже, що щось сталося, API каже точно що.
- Після останньої спроби подія відкидається і залишається видимою у вашому журналі подій. Ви можете відтворити її вручну.