Fiatside

Developers

Вебхуки

Вебхук сообщает вам об изменении состояния заказа. Он не заменяет чтение заказа: это уведомление, а не источник истины.

Опубликованный контракт, еще не открытый

Вебхуки сейчас не отправляются. Их формат, подпись и политика повторов определены и опубликованы здесь, чтобы ваш получатель мог быть написан и протестирован заранее.

01

События

Каждое событие имеет одинаковую оболочку: 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Средства возвращены только на исходный адрес, за вычетом сетевых комиссий.
02

Полезная нагрузка

Постоянная оболочка, независимо от типа.

Примерjson
{
  "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"
  }
}
03

Подпись

Каждая доставка содержит заголовок подписи: временную метку и HMAC-SHA256, вычисленный по этой временной метке, точке и СЫРОМУ телу запроса.

Fiatside-Signaturehttp
Fiatside-Signature: t=<timestamp unix>,v1=<hex hmac-sha256>
Проверка, Node.jstypescript
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 до обработки. Помещайте событие в свою очередь и обрабатывайте его после: длительная обработка вызывает тайм-аут и, следовательно, повтор, даже если вы действительно получили событие.
04

Повторы и порядок

Любой статус, отличный от 2xx, или отсутствие ответа вообще вызывает повтор по расписанию ниже.

Повторы и порядок
ПопыткаЗадержка
1немедленно
230 s
32 min
410 min
51 h
66 h
  • Порядок прибытия не гарантирован. Событие выплаты может прийти до события подтверждения, которое логически ему предшествует: доверяйте состоянию заказа, а не порядку получения.
  • Одно и то же событие может прийти дважды. Сохраняйте идентификатор события и игнорируйте уже обработанный идентификатор: идемпотентность на вашем приёмнике — ваша ответственность, и её легко обеспечить.
  • Никогда не доверяйте суммам в полезной нагрузке для зачисления чего-либо на вашей стороне. Перечитайте заказ через API: полезная нагрузка говорит, что что-то произошло, API говорит, что именно.
  • После последней попытки событие отбрасывается и остаётся видимым в вашем журнале событий. Вы можете воспроизвести его вручную.