What you get

A Telegram message (private chat or group) when an invoice charge fails: customer, email, amount, and invoice link.

You need a bot (BotFather), token, and chat_id. First you prove the destination with curl; wiring a signed Stripe sender is the next step.

Flow
  1. payment_failed event
  2. Your code maps it
  3. Telegram shows the alert

Event JSON: Stripe webhooks. Bot and chat_id: Alerts with a Telegram bot.

Requirements

  • A Telegram bot with its token and the chat_id, and Start already tapped
  • For the first half, nothing else
  • For the second: a Stripe account in test mode and somewhere to host the receiver — see the warning below

Bot and chat

  1. If you do not have a bot yet: @BotFather (opens in a new tab)/newbot → name and a username that ends in bot → copy the token.
  2. Open the t.me/… link BotFather gives you, tap Start (or add the bot to a group with permission to send; group ids are usually negative).
  3. Get the chat_id (replace paste-your-token-here with the BotFather token):
Language: bash
TOKEN="paste-your-token-here"
curl -sS "https://api.telegram.org/bot${TOKEN}/getUpdates"

Look for:

Language: json

"chat": {
  "id": 123456789,
  "type": "private"
}

That "id" is the chat_id. If result is empty, send another message to the bot and try again. Detail and groups: Telegram bot alerts.

Test the destination

Also replace 123456789 with your chat_id.

Language: bash
TOKEN="paste-your-token-here"
CHAT_ID="123456789"
curl -sS -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \
  -H "Content-Type: application/json" \
  -d "{\"chat_id\":${CHAT_ID},\"text\":\"Failed payment (test)\
Customer: cus_demo\
Email: demo@example.com\
Amount: 19.00 EUR\
Event: invoice.payment_failed\"}"

Expected result: "ok": true and the message in Telegram.

That proves the alert format and that the bot delivers to the chat. It does not prove a signed delivery from Stripe.

What you will map from Stripe

Stripe fieldIn the message
customerCustomer
customer_emailEmail
amount_due / currencyAmount (cents → units; 1900 + eur = 19.00 EUR)
Invoice idInvoice
Event typeEvent
hosted_invoice_urlLink (when present)

Start with plain text (no parse_mode) so you do not fight escapes.

Wire Stripe

You already have the message rendered in Telegram. But Stripe cannot talk to Telegram directly: something has to receive the event, check it really came from Stripe, and translate it into the message text.

That is not a single curl. Options:

No-code (Zapier, Make, etc.)
Faster if you do not want to write or host code.
Your own HTTPS endpoint
Stripe Dashboard → Developers → Webhooks → URL that verifies `Stripe-Signature`, then calls sendMessage.
Stripe CLI locally
Dev only: forwards to a process **you** run on a port; without that process there is no Telegram alert.

If you take the build-it-yourself path, the receiver does this, in this order. It is not code to copy, it is the script:

  1. Receive the POST with the raw body.
  2. Verify the signature with the signing secret (Stripe docs (opens in a new tab)).
  3. If type === "invoice.payment_failed", map invoice → text (cents → units).
  4. POST to https://api.telegram.org/bot<TOKEN>/sendMessage with chat_id + text.
  5. Return 2xx to Stripe quickly.
  6. Dedupe on evt_… or invoice.id.

CLI example once you already have something listening on a local port (e.g. 4242):

Language: bash

stripe listen --forward-to localhost:4242/webhook
# in another terminal:
stripe trigger invoice.payment_failed

You can stop after the Telegram destination test and leave the Stripe glue for when you have somewhere to host the receiver. Raw event shape: Stripe webhooks.

Docs: Stripe webhooks (opens in a new tab) · Bot API sendMessage (opens in a new tab).

Security

  • If the token leaks: BotFather → /revoke, update env/secrets.
  • Keep Stripe test and live endpoints separate.
  • An ops-alerts-only bot limits blast radius.
  • Plan for retries: Stripe may emit several payment_failed events for one invoice.

Common mistakes

What you seeLikely causeWhat to do
No chat_idNo Start / no updatesOpen the bot, Start, retry getUpdates
UnauthorizedRevoked or mistyped tokenNew token from BotFather
chat not foundchat_id from another chat or botRediscover the id
Silent with curl OKLooking at the wrong chat or groupConfirm the bot chat
Stripe “delivery failed”Endpoint without 2xx or bad signatureReceiver logs; secret for that specific endpoint
Many messages for one customerRetries without dedupeFilter by invoice or space out alerts
Telegram 400Malformed JSON or parse_mode without escapingStart with plain text

Alternatives

Stripe email
Zero code; less visible on mobile.
Another chat Incoming Webhook
Same event, different destination (e.g. Discord or Slack).
Another source on the same bot
You can reuse token and chat_id for non-Stripe alerts.

Resources