Riferimento API: webhook
I webhook inviano gli eventi al tuo server dopo che si verificano. Imposta l’URL del webhook (facoltativo) sulla tua app in Altro → Merchant API: da lì in poi la piattaforma manda in POST un corpo JSON firmato per ogni evento a cui sei iscritto.
La struttura del messaggio
{
"update_id": 123,
"update_type": "invoice_paid",
"request_date": "2026-08-11T12:00:00Z",
"payload": { … }
}
update_id resta lo stesso a ogni riconsegna dello stesso evento: usa quello
per deduplicare. request_date viene registrato a ogni tentativo di consegna.
payload è l’oggetto completo del tipo di evento: l’oggetto fattura per gli
eventi delle fatture, l’oggetto buono, trasferimento o abbonamento (le loro forme
stanno nelle rispettive pagine di riferimento).
I tipi di evento
update_type | Quando parte | Payload |
|---|---|---|
invoice_paid | una fattura viene pagata — non richiede attivazione | oggetto fattura |
invoice_expired | una fattura supera la scadenza senza essere pagata | oggetto fattura |
check_activated | viene riscattato un buono o la quota di un destinatario | oggetto buono; il riscatto da un elenco nominativo include anche activated_slot |
check_expired | scade un buono nominativo e le quote non riscosse tornano all’app | oggetto buono con recipients e i relativi valori claimed_at |
transfer_completed | un trasferimento viene completato o un pagamento sulla blockchain viene confermato | oggetto trasferimento o prelievo |
transfer_returned | un trasferimento in attesa non riscosso torna all’app, oppure un pagamento sulla blockchain fallisce e l’importo bloccato viene restituito | oggetto trasferimento o prelievo |
refund_completed | un rimborso viene eseguito | oggetto fattura con i campi refunded_* |
subscription_activated | un utente approva un piano | oggetto abbonamento |
subscription_charged | viene incassato il pagamento di un periodo — charge.kind dice quale: initial, renewal o resubscribe | oggetto abbonamento + charge: {period_no, kind, asset, amount, fee, paid_at} |
subscription_cancelled | un abbonamento viene disdetto da una delle due parti | oggetto abbonamento |
subscription_expired | il periodo di tolleranza finisce senza pagamento | oggetto abbonamento |
I pagamenti sulla blockchain creati nell’app usano gli stessi eventi dei
trasferimenti. Il payload contiene kind: "withdrawal", campi della blockchain
come network, address e txid, e lo stato completed o failed. Il loro
transfer_id appartiene a una sequenza di ID separata: distingui i payload
tramite kind prima di usarlo. Il riferimento OpenAPI scaricabile descrive
entrambe le varianti.
Tutto tranne invoice_paid è facoltativo (è un’estensione rispetto a
Crypto Bot: un’integrazione che gestisce solo gli eventi di Crypto Bot non
riceve un update_type sconosciuto, a meno che tu non lo abbia attivato).
Si attivano per app sotto Eventi webhook aggiuntivi, nella sezione Webhook
dell’app. Le opzioni usano gli identificativi della tabella qui sopra.
Salva un URL per i webhook per ricevere gli eventi.
Verificare la firma
Ogni consegna porta l’intestazione TgPayCrypto-API-Signature
(TgCryptoPay-API-Signature e Crypto-Pay-API-Signature ne sono alias di
compatibilità, con lo stesso valore): l’HMAC-SHA256 in esadecimale del corpo
grezzo della richiesta, con chiave il digest SHA-256 del token API principale della tua app. È lo schema
di Crypto Bot, quindi il codice di verifica che hai già funziona senza
modifiche:
import hashlib, hmac
secret = hashlib.sha256(API_TOKEN.encode()).digest()
expected = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
ok = hmac.compare_digest(expected, headers["TgPayCrypto-API-Signature"])
Verifica sui byte grezzi ricevuti: un JSON riserializzato dopo il parsing può differire byte per byte e far fallire il controllo. A firmare è solo il token principale; i token con permessi limitati non firmano mai. Rigenerare il token principale cambia all’istante la chiave di firma dei webhook, quindi aggiorna il segreto sul tuo server nello stesso momento.
Consegne e tentativi
- Una consegna si considera riuscita con una risposta 2xx ricevuta prima del timeout del tentativo. Rispondi rapidamente ed esegui separatamente le operazioni lunghe.
- Uno stato di errore, un timeout o un problema di connessione avviano nuovi
tentativi con attese crescenti, fino al limite previsto dalla piattaforma.
La consegna non è garantita: usa
getInvoicesper riconciliare i pagamenti dopo un’interruzione. - Dopo l’ultimo tentativo la consegna viene abbandonata. L’URL del webhook, però, non viene mai disattivato automaticamente, neanche se il tuo endpoint risponde in modo intermittente.
- Siccome i tentativi si ripetono, il tuo gestore dev’essere idempotente:
deduplica su
update_idprima di agire.
⚠️ Verifica prima di evadere
Al tuo URL può mandare un POST chiunque. Finché il controllo della firma non
passa, tratta il corpo come input non fidato: non spedire un ordine, non
accreditare un utente, non segnare niente come pagato. L’ordine giusto è:
verifica la firma → deduplica su update_id → agisci.
Questa guida ti è stata utile?
Grazie del riscontro.