Developers
Вебхуки
Вебхук сообщает вам об изменении состояния заказа. Он не заменяет чтение заказа: это уведомление, а не источник истины.
Опубликованный контракт, еще не открытый
Вебхуки сейчас не отправляются. Их формат, подпись и политика повторов определены и опубликованы здесь, чтобы ваш получатель мог быть написан и протестирован заранее.
События
Каждое событие имеет одинаковую оболочку: id, тип, временную метку и объект данных. Подписывайтесь на типы, которые вы обрабатываете, и молча игнорируйте остальные — новые типы будут добавляться, и получатель, который падает на неизвестном типе, ломает сам себя.
| Тип | Что означает |
|---|---|
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 говорит, что именно.
- После последней попытки событие отбрасывается и остаётся видимым в вашем журнале событий. Вы можете воспроизвести его вручную.