Fiatside

Developers

Webhook

Un webhook ti dice che un ordine ha cambiato stato. Non sostituisce la lettura dell'ordine: è una notifica, non una fonte di verità.

Contratto pubblicato, non ancora aperto

I webhook non vengono emessi oggi. La loro forma, firma e politica di retry sono stabilite e pubblicate qui, così il tuo ricevitore può essere scritto e testato in anticipo.

01

Eventi

Ogni evento ha lo stesso involucro: un id, un tipo, un timestamp e un oggetto data. Sottoscrivi i tipi che gestisci e ignora silenziosamente gli altri — verranno aggiunti nuovi tipi, e un ricevitore che fallisce su un tipo sconosciuto rompe se stesso.

Eventi
TipoCosa significa
order.createdL'ordine è creato e il tasso è bloccato. L'indirizzo di deposito è assegnato.
order.deposit_detectedUna transazione in arrivo è vista sulla rete, prima della conferma. Usa questo evento per rassicurare l'utente, mai per consegnare qualcosa.
order.confirmingIl contatore di conferme sta avanzando. Emesso a ogni passo, non a ogni blocco.
order.deposit_confirmedIl numero di conferme richiesto dalla rete è stato raggiunto.
order.underpaidL'importo ricevuto è inferiore all'importo previsto oltre la tolleranza. L'ordine non è perso: attende una scelta tra integrazione, continuazione con l'importo ricevuto o rimborso.
order.overpaidL'importo ricevuto supera l'importo previsto. L'importo iniziale rimane al tasso bloccato; l'eccedenza è gestita separatamente.
order.payout_sentIl trasferimento è partito sul circuito. Attenzione: inviato non è ricevuto — il ritardo residuo dipende dalla banca del beneficiario.
order.completedIl regolamento è confermato dal circuito. Stato terminale.
order.payout_failedIl circuito ha respinto o restituito il trasferimento, con il suo motivo. Nessun nuovo tentativo automatico viene effettuato su un ritorno bancario: la causa è quasi sempre nei dettagli.
order.refundedI fondi sono stati restituiti, solo all'indirizzo di origine, al netto delle commissioni di rete.
02

Il payload

Un involucro costante, qualunque sia il tipo.

Esempiojson
{
  "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

Firma

Ogni consegna porta un header di firma: un timestamp e un HMAC-SHA256 calcolato su quel timestamp, un punto e il corpo della richiesta RAW.

Fiatside-Signaturehttp
Fiatside-Signature: t=<timestamp unix>,v1=<hex hmac-sha256>
Verifica, 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)
}
  • Firma il corpo grezzo, prima di qualsiasi parsing. Ri-serializzare l'oggetto JSON cambia l'ordine delle chiavi e gli spazi bianchi: la firma non corrisponderà più e cercherai a lungo.
  • Confronta in tempo costante. Un confronto === si ferma al primo byte diverso, il che consente a un attaccante di misurare la firma attesa byte per byte.
  • Rifiuta oltre la finestra di tolleranza di 300 secondi. Senza questo controllo, una richiesta valida catturata ieri rimane riproducibile oggi.
  • Rispondi 200 prima di elaborare. Metti l'evento nella tua coda e gestiscilo dopo: un'elaborazione lunga provoca un timeout e quindi un retry, anche se hai ricevuto l'evento.
04

Retry e ordinamento

Qualsiasi stato non-2xx, o nessuna risposta, attiva un retry secondo la pianificazione seguente.

Retry e ordinamento
TentativoRitardo
1immediato
230 s
32 min
410 min
51 h
66 h
  • L'ordine di arrivo non è garantito. Un evento di pagamento può arrivare prima dell'evento di conferma che logicamente lo precede: fidati dello stato dell'ordine, non dell'ordine di ricezione.
  • Lo stesso evento può arrivare due volte. Conserva l'id dell'evento e ignora un id già gestito: l'idempotenza sul tuo ricevitore è una tua responsabilità, ed è facile da ottenere.
  • Non fidarti mai degli importi nel payload per accreditare qualsiasi cosa dalla tua parte. Rileggi l'ordine tramite l'API: il payload dice che qualcosa è successo, l'API dice esattamente cosa.
  • Dopo l'ultimo tentativo l'evento viene eliminato e rimane visibile nel tuo registro eventi. Puoi riprodurlo manualmente.