tgpay cryptoAPI
crypto-payapiwebhookssignature

Referencia de la API: webhooks

5 min de lecturaActualizado el 2 nov 2026

Los webhooks envían eventos a tu servidor después de que ocurren. Pon la URL del webhook (opcional) en tu app, en Más → API para comerciantes; a partir de ahí la plataforma hace POST de un cuerpo JSON firmado por cada evento al que estés suscrito.

La estructura del mensaje

{
  "update_id": 123,
  "update_type": "invoice_paid",
  "request_date": "2026-08-11T12:00:00.123456Z",
  "payload": { … }
}

update_id se mantiene estable entre reenvíos del mismo evento: basa tu deduplicación en él. request_date (UTC, ISO 8601 con microsegundos) se registra en cada intento de entrega. payload es el objeto completo del tipo de evento: el objeto de factura para los eventos de facturas, el objeto de cheque, de transferencia o de suscripción (las estructuras están en las páginas de cada método: facturas, transferencias y cheques, suscripciones).

Tipos de eventos

update_typeSe dispara cuandoPayload
invoice_paidse paga una factura — no requiere activación adicionalobjeto de factura
invoice_expireduna factura pasa su fecha límite sin pagarseobjeto de factura
check_activatedse canjea un cheque o la parte de un destinatarioobjeto de cheque; el canje de una parte de un cheque con lista de destinatarios también incluye activated_slot
check_expiredexpira un cheque con lista de destinatarios y las partes sin canjear se devuelven a la appobjeto de cheque con status: "expired", recipients y sus valores claimed_at
transfer_completedse completa una transferencia o se confirma un pago on-chainobjeto de transferencia o de retiro
transfer_returneduna transferencia pendiente sin reclamar se devuelve a la app, o un pago on-chain falla y se libera el monto retenidoobjeto de transferencia o de retiro
refund_completedse ejecuta un reembolsoobjeto de factura con los campos refunded_*
subscription_activatedun usuario aprueba un planobjeto de suscripción
subscription_chargedse cobra un período — charge.kind dice cuál: initial, renewal o resubscribeobjeto de suscripción + charge: {period_no, kind, asset, amount, fee, paid_at}
subscription_cancelleduna suscripción se cancela por cualquiera de los dos ladosobjeto de suscripción
subscription_expiredel período de gracia se agota sin pagoobjeto de suscripción

Los pagos on-chain creados en la app usan estos mismos eventos de transferencia. Su payload incluye kind: "withdrawal", campos de la blockchain como network, address y txid, y el estado completed o failed. Su transfer_id pertenece a una secuencia de ID independiente. Solo esta variante tiene kind: el payload de una transferencia normal no incluye el campo kind, así que trata un payload con kind: "withdrawal" como un pago on-chain y uno sin kind como una transferencia. La referencia OpenAPI descargable describe ambas variantes.

Todo salvo invoice_paid es opcional (una extensión sobre Crypto Bot: una integración que solo admite los eventos de Crypto Bot no recibe un update_type desconocido a menos que lo actives). Actívalos por app en Eventos de webhook adicionales, dentro de la sección Webhook de la app. Los interruptores usan los identificadores de la tabla anterior. Guarda una URL de webhook para recibir los eventos.

Verificar la firma

Cada entrega lleva la cabecera TgPayCrypto-API-Signature (TgCryptoPay-API-Signature y Crypto-Pay-API-Signature son alias de compatibilidad con el mismo valor): el HMAC-SHA256 en hexadecimal del cuerpo original de la petición, sin modificar, con clave el resumen SHA-256 del token de API principal de tu app. Es el esquema de Crypto Bot, así que el código de verificación que ya tengas funciona sin cambios:

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 sobre los bytes originales recibidos: si analizas el JSON y vuelves a serializarlo, los bytes pueden cambiar y hacer fallar la comprobación. Solo firma el token principal; los tokens restringidos nunca firman. Renovar el token principal vuelve a fijar al instante la clave de firma de los webhooks, así que actualiza el secreto en tu servidor al mismo tiempo.

Entrega y reintentos

  • Una entrega se considera exitosa si recibe una respuesta 2xx en menos de 10 segundos. Responde pronto y procesa las tareas largas por separado. Las redirecciones no se siguen: un 3xx cuenta como fallo, así que registra la URL final.
  • Un estado de error, un tiempo de espera agotado o un fallo de conexión provoca reintentos: hasta 17 intentos, con una espera que empieza en 10 segundos y se duplica hasta 8 horas (con algo de variación aleatoria), unos 3 días en total. La entrega no está garantizada; usa getInvoices para comprobar los pagos después de una interrupción del servicio.
  • Cada intento se envía a la URL del webhook guardada en ese momento: si la cambias, la nueva URL recibe los reintentos pendientes, y si la eliminas, esos reintentos se descartan.
  • Después del último intento se descarta la entrega. La URL del webhook no se desactiva automáticamente: los fallos de un endpoint no cancelan la suscripción a los eventos.
  • Como hay reintentos, tu manejador tiene que ser idempotente: deduplica por update_id antes de actuar.

⚠️ Verifica antes de despachar

Cualquiera puede hacer POST a tu URL de webhook. Hasta que la comprobación de la firma pase, trata el cuerpo como entrada no confiable: no despaches un pedido, no le acredites nada a un usuario, no marques nada como pagado. El orden seguro es: verificar la firma → deduplicar por update_id → actuar.