tgpay cryptoAPI
crypto-payapiwebhookssignature

API reference: webhooks

3 min readUpdated Oct 9, 2026

Webhooks push events to your server after they happen. Set the Webhook URL (optional) 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, the transfer object, or the subscription object (shapes on the respective reference pages).

Event types

update_typeFires whenPayload
invoice_paidan invoice is paid — no opt-in requiredinvoice object
invoice_expiredan invoice passes its deadline unpaidinvoice object
check_activateda check or one recipient’s share is claimedcheck object; a named-list claim also has activated_slot
check_expireda named-list check expires and unclaimed shares return to the appcheck object with recipients and their claimed_at values
transfer_completeda transfer completes, or an on-chain payout is confirmedtransfer or withdrawal object
transfer_returnedan unclaimed pending transfer returns to the app, or an on-chain payout fails and its hold is returnedtransfer or withdrawal object
refund_completeda refund is executedinvoice object with the refunded_* fields
subscription_activateda user approves a plansubscription object
subscription_chargedpayment for a period is collected — 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

On-chain payouts created in the app use these same transfer events. Their payload has kind: "withdrawal", chain fields such as network, address and txid, and status completed or failed. Their transfer_id belongs to a separate ID sequence; distinguish payloads by kind before using it. The downloadable OpenAPI reference describes both payload variants.

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 in the app’s Webhook section — the toggles use the identifiers from the table above. Save a webhook URL to receive events.

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 a 2xx response received before the attempt times out. Respond promptly and process long tasks separately.
  • An error status, timeout or connection failure triggers retries with increasing delays, up to the platform’s attempt limit. Delivery is not guaranteed; use getInvoices to reconcile payments after an outage.
  • 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.