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
200invece 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
amountecurrencydove l'API portaamount_minoreasset_id, emerchant_referencedove essa portaclient_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.stringifydi 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.