tgpay cryptoAPI
crypto-payapiwebhookssignature

API reference: webhooks

3 min readUpdated Aug 11, 2026

Webhooks push events to your server the moment they happen. Set the Webhook URL on your app in More → Merchant API; the platform then POSTs a signed JSON body for every event you’re subscribed to.

The envelope

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

update_id is stable across redeliveries of the same event — key your deduplication on it. request_date is stamped per delivery attempt. payload is the full object for the event type: the invoice object for invoice events, the check object, or the subscription object (shapes on the respective reference pages).

Event types

update_typeFires whenPayload
invoice_paidan invoice is paid — always deliveredinvoice object
invoice_expiredan invoice passes its deadline unpaidinvoice object
check_activatedone of your checks is claimedcheck object
refund_completeda refund is executedinvoice object with the refunded_* fields
subscription_activateda user approves a plansubscription object
subscription_chargeda period is billed — charge.kind says which: initial, renewal, or resubscribesubscription object + charge: {period_no, kind, asset, amount, fee, paid_at}
subscription_cancelleda subscription is canceled by either sidesubscription object
subscription_expiredthe grace period runs out unpaidsubscription object

Everything except invoice_paid is opt-in (an extension over Crypto Bot — a strict Crypto Bot-shaped consumer never meets an unknown update_type unless you asked for it). Opt in per app under Extra webhook events on the Merchant API screen — the toggles appear once a webhook URL is set, and they’re labeled with the raw identifiers from the table above.

Verifying the signature

Every delivery carries the TgPayCrypto-API-Signature header (TgCryptoPay-API-Signature and Crypto-Pay-API-Signature are compatibility aliases with the same value): the hex HMAC-SHA256 of the raw request body, keyed by the SHA-256 digest of your app’s main API token. It’s the Crypto Bot scheme, so existing verification code works unchanged:

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"])

Verify over the raw received bytes — a re-serialized parse can differ byte-for-byte and fail the check. Only the main token signs; restricted tokens never do. Rotating the main token re-keys webhook signing instantly, so update the secret on your server in the same move.

Delivery and retries

  • A delivery counts as successful on any 2xx response within 10 seconds.
  • Anything else — an error status, a timeout, a connection failure — is retried with exponential backoff: the first retry after about 10 seconds, the gap doubling up to 8 hours, for up to 17 attempts spread over roughly 3 days.
  • After the last attempt the delivery is dropped. The webhook URL itself is never disabled automatically — a flaky endpoint doesn’t silently unsubscribe your app.
  • Because retries happen, your handler must be idempotent: dedupe on update_id before acting.

⚠️ Verify before you fulfill

Anyone can POST to your webhook URL. Until the signature check passes, treat the body as untrusted input: don’t ship an order, don’t credit a user, don’t mark anything paid. The safe order is: verify the signature → dedupe on update_id → act.