Fiatside

Developers

ウェブフック

Webhookは、注文の状態が変わったことを通知します。これは注文の読み取りを置き換えるものではありません。通知であり、真実のソースではありません。

公開された契約、まだ公開されていません

Webhookは現在発行されていません。その形状、署名、再試行ポリシーはここで確定され公開されているため、受信側を事前に作成してテストできます。

01

イベント

すべてのイベントは同じエンベロープを運びます。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資金は、ネットワーク手数料を差し引いた上で、送金元アドレスにのみ返金されました。
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

署名

各配信には署名ヘッダーが含まれます。タイムスタンプと、そのタイムスタンプ、ドット、およびRAWリクエストボディに対して計算された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
  • 到着順は保証されません。論理的に先行する確認イベントの前に送金イベントが到着することがあります。受信順ではなく、注文状態を信頼してください。
  • 同じイベントが2回届くことがあります。イベントIDを保存し、すでに処理済みのIDは無視してください。受信側での冪等性はお客様の責任であり、簡単に実現できます。
  • ペイロードの金額を信頼して、お客様側で何かを入金することは絶対にしないでください。APIを通じて注文を再読込してください。ペイロードは何かが起こったことを示し、APIは正確に何が起こったかを示します。
  • 最後の試行後、イベントは破棄され、イベントログに表示されたままになります。手動で再生できます。