Referencia de la API: webhooks
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:00Z",
"payload": { … }
}
update_id se mantiene estable entre reenvíos del mismo evento: basa tu
deduplicación en él. request_date 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 referencia
correspondientes).
Tipos de eventos
update_type | Se dispara cuando | Payload |
|---|---|---|
invoice_paid | se paga una factura — no requiere activación adicional | objeto de factura |
invoice_expired | una factura pasa su fecha límite sin pagarse | objeto de factura |
check_activated | se canjea un cheque o la parte de un destinatario | objeto de cheque; el canje de una parte de un cheque con lista de destinatarios también incluye activated_slot |
check_expired | expira un cheque con lista de destinatarios y las partes sin canjear se devuelven a la app | objeto de cheque con recipients y sus valores claimed_at |
transfer_completed | se completa una transferencia o se confirma un pago on-chain | objeto de transferencia o de retiro |
transfer_returned | una transferencia pendiente sin reclamar se devuelve a la app, o un pago on-chain falla y se libera el monto retenido | objeto de transferencia o de retiro |
refund_completed | se ejecuta un reembolso | objeto de factura con los campos refunded_* |
subscription_activated | un usuario aprueba un plan | objeto de suscripción |
subscription_charged | se cobra un período — charge.kind dice cuál: initial, renewal o resubscribe | objeto de suscripción + charge: {period_no, kind, asset, amount, fee, paid_at} |
subscription_cancelled | una suscripción se cancela por cualquiera de los dos lados | objeto de suscripción |
subscription_expired | el período de gracia se agota sin pago | objeto 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; distingue los payloads por kind antes
de usarlo. 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 antes de agotar el tiempo de espera. Responde pronto y procesa las tareas largas por separado.
- Un estado de error, un tiempo de espera agotado o un fallo de conexión provoca
reintentos con intervalos cada vez mayores, hasta el límite de intentos de la
plataforma. La entrega no está garantizada; usa
getInvoicespara comprobar los pagos después de una interrupción del servicio. - 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_idantes 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.
¿Te sirvió este artículo?
Gracias por tu comentario.