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:00.123456Z",
"payload": { … }
}
update_id resta lo stesso a ogni riconsegna dello stesso evento: usa quello
per deduplicare. request_date (UTC, ISO 8601 con microsecondi) 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 rispettive
strutture sono descritte nelle pagine dei metodi: fatture,
trasferimenti e buoni,
abbonamenti).
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 status: "expired", 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. Solo questa variante ha
kind: il payload di un trasferimento normale non ha proprio il campo kind,
quindi tratta un payload con kind: "withdrawal" come un pagamento sulla
blockchain e uno senza kind come un trasferimento. 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 entro 10 secondi. Rispondi rapidamente ed esegui separatamente le operazioni lunghe. I redirect non vengono seguiti: un 3xx conta come errore, quindi registra l’URL finale.
- Uno stato di errore, un timeout o un problema di connessione avviano nuovi
tentativi: fino a 17, con un’attesa che parte da 10 secondi e raddoppia fino
a 8 ore (con una piccola variazione casuale), circa 3 giorni in tutto.
La consegna non è garantita: usa
getInvoicesper riconciliare i pagamenti dopo un’interruzione. - Ogni tentativo va all’URL del webhook salvato in quel momento: un URL cambiato riceve i tentativi in sospeso, e se rimuovi l’URL questi vengono scartati.
- 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.