tgpay cryptoAPI
crypto-payapiwebhookssignature

Riferimento API: webhook

4 min di letturaAggiornata il 2 nov 2026

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_typeQuando partePayload
invoice_paiduna fattura viene pagata — non richiede attivazioneoggetto fattura
invoice_expireduna fattura supera la scadenza senza essere pagataoggetto fattura
check_activatedviene riscattato un buono o la quota di un destinatariooggetto buono; il riscatto da un elenco nominativo include anche activated_slot
check_expiredscade un buono nominativo e le quote non riscosse tornano all’appoggetto buono con status: "expired", recipients e i relativi valori claimed_at
transfer_completedun trasferimento viene completato o un pagamento sulla blockchain viene confermatooggetto trasferimento o prelievo
transfer_returnedun trasferimento in attesa non riscosso torna all’app, oppure un pagamento sulla blockchain fallisce e l’importo bloccato viene restituitooggetto trasferimento o prelievo
refund_completedun rimborso viene eseguitooggetto fattura con i campi refunded_*
subscription_activatedun utente approva un pianooggetto abbonamento
subscription_chargedviene incassato il pagamento di un periodo — charge.kind dice quale: initial, renewal o resubscribeoggetto abbonamento + charge: {period_no, kind, asset, amount, fee, paid_at}
subscription_cancelledun abbonamento viene disdetto da una delle due partioggetto abbonamento
subscription_expiredil periodo di tolleranza finisce senza pagamentooggetto 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 getInvoices per 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_id prima 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.