neo-pays

Webhook

La fonte di verità delle sue integrazioni: ogni cambiamento di stato le viene inviato, firmato, con riconsegna automatica.

Creare un endpoint

POST /v1/webhooks
{
  "url": "https://ilsuosito.it/webhook/neopays",
  "description": "produzione",
  "subscribed_events": ["payment.success", "refund.success"]
}

La risposta porta il segreto di firma. subscribed_events vuoto = tutti gli eventi; un evento fuori catalogo è rifiutato invece che memorizzato, perché aspetterebbe una consegna che nulla emette.

Questa chiamata garantisce, non sempre crea. Un URL già registrato torna con 200 invece di essere duplicato — uno script di provisioning eseguito due volte lascia UN endpoint, non due che ricevono ogni evento in doppio. Il segreto è restituito in entrambi i casi, perché il motivo legittimo per richiamare è averlo perso.

GET    /v1/webhooks              — i suoi endpoint e i loro contatori
GET    /v1/webhooks/{id}         — un endpoint
PATCH  /v1/webhooks/{id}         — modifica parziale
DELETE /v1/webhooks/{id}         — eliminazione definitiva
GET    /v1/webhooks/{id}/secret  — rileggere il segreto
POST   /v1/webhooks/test         — consegna di prova a tutti i suoi endpoint attivi

Il PATCH cambia solo ciò che lei invia: omettere is_active non disattiva l'endpoint. Per fermare le consegne senza perdere l'endpoint né il suo segreto, invii is_active: false invece di eliminare.

Dieci endpoint per conto al massimo. La dashboard fa tutto questo, e solo lei sa ripetere una consegna fallita e ruotare un segreto.

Gli eventi

Evento Quando
payment.initiated Incasso creato
payment.pending In attesa della conferma del pagatore
payment.success Incassato — l'evento che fa fede
payment.failed Fallimento
payout.success / payout.failed / payout.cancelled Ciclo di vita di un pagamento
payout.refunded Pagamento restituito dal rail in un secondo momento
refund.success / refund.failed Ciclo di vita di un rimborso

La busta è sempre la stessa:

{
  "event": "payment.success",
  "created": "2026-08-24T12:00:00Z",
  "data": {
    "id": "op_01J8Z9K2QW",
    "reference": "NP-...",
    "merchant_reference": "ordine-1042",
    "amount": "5000",
    "currency": "XOF",
    "status": "success"
  }
}

Il corpo del webhook non ha la stessa forma della risposta dell'API. Porta amount e currency dove l'API porta amount_minor e asset_id, e merchant_reference dove essa porta client_ref. È un contratto distinto, scritto prima dell'altro e immutato da allora: non dia per scontato che un oggetto dell'uno si decodifichi come un oggetto dell'altro.

Verificare la firma

Ogni consegna porta l'header:

X-NeoPays-Signature: t=<marca temporale unix>,v1=<hmac>

dove hmac = HMAC-SHA256(segreto, "<t>.<corpo grezzo>"), in esadecimale. Verifichi in tre tempi:

const crypto = require('node:crypto')

function verify(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(',').map(kv => kv.split('=')))
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > toleranceSec) return false
  const expected = crypto.createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`).digest('hex')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
}

Il corpo GREZZO, non quello riserializzato. Calcoli l'HMAC sui byte ricevuti, prima di qualsiasi analisi JSON. Un JSON.stringify di ritorno può riordinare le chiavi e invalidare la firma.

Durante una rotazione di segreto l'header porta DUE voci v1=, il segreto corrente per primo. Le verifichi tutte, non solo la prima: il giorno in cui sposterà la sua configurazione sul segreto nuovo prima che il passaggio lato piattaforma sia concluso, la prima firma è quella del segreto vecchio, e un verificatore che si ferma alla prima rifiuta allora il 100 % delle consegne.

Riconsegna

Risponda 2xx entro pochi secondi (confermi la ricezione, elabori dopo). Qualsiasi altra risposta avvia la scala dei tentativi: 5 tentativi in tutto, dopo 60 s, 5 min, 30 min e 2 h.

Un endpoint che fallisce 20 consegne consecutive è sospeso per un'ora (le consegne riprendono da sole); un solo successo — compresa una consegna di prova dalla dashboard — lo riabilita subito.

Idempotenza lato ricevente

Le consegne sono at-least-once: lo stesso evento può arrivare due volte. Deduplichi sulla coppia (event, data.id) o registri gli identificativi degli eventi già trattati.