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.
- payment_failed event
- Your code maps it
- 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
- If you do not have a bot yet: @BotFather (opens in a new tab) →
/newbot→ name and a username that ends inbot→ copy the token. - Open the
t.me/…link BotFather gives you, tap Start (or add the bot to a group with permission to send; groupids are usually negative). - Get the
chat_id(replacepaste-your-token-herewith the BotFather token):
TOKEN="paste-your-token-here"
curl -sS "https://api.telegram.org/bot${TOKEN}/getUpdates"Look for:
"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.
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 field | In the message |
|---|---|
customer | Customer |
customer_email | |
amount_due / currency | Amount (cents → units; 1900 + eur = 19.00 EUR) |
Invoice id | Invoice |
Event type | Event |
hosted_invoice_url | Link (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:
- Receive the
POSTwith the raw body. - Verify the signature with the signing secret (Stripe docs (opens in a new tab)).
- If
type === "invoice.payment_failed", map invoice → text (cents → units). POSTtohttps://api.telegram.org/bot<TOKEN>/sendMessagewithchat_id+text.- Return
2xxto Stripe quickly. - Dedupe on
evt_…orinvoice.id.
CLI example once you already have something listening on a local port (e.g. 4242):
stripe listen --forward-to localhost:4242/webhook
# in another terminal:
stripe trigger invoice.payment_failedYou 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_failedevents for one invoice.
Common mistakes
| What you see | Likely cause | What to do |
|---|---|---|
No chat_id | No Start / no updates | Open the bot, Start, retry getUpdates |
Unauthorized | Revoked or mistyped token | New token from BotFather |
chat not found | chat_id from another chat or bot | Rediscover the id |
| Silent with curl OK | Looking at the wrong chat or group | Confirm the bot chat |
| Stripe “delivery failed” | Endpoint without 2xx or bad signature | Receiver logs; secret for that specific endpoint |
| Many messages for one customer | Retries without dedupe | Filter by invoice or space out alerts |
| Telegram 400 | Malformed JSON or parse_mode without escaping | Start 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.