What you get
A Discord channel with a red embed when an invoice charge fails: customer, email, amount, and event type.
No Discord bot needed: an Incoming Webhook is enough — a secret address Discord gives you to post into a channel, with no other permissions. First you check the alert looks right; wiring Stripe up for real comes after.
- payment_failed event
- Your code maps it
- Discord shows the embed
For the event JSON: Stripe webhooks.
Requirements
- A Discord channel where you are an admin — admins are the ones who can create webhooks
- 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
Create the Discord webhook
- Open the channel (prefer a dedicated one, e.g.
#billing-failures). - Channel gear → Integrations → Webhooks → New Webhook.
- Clear name, e.g.
Stripe failures. - Copy Webhook URL.
That URL is a secret. Do not paste it into issues, public chats, or the repo.
Test the destination
Replace the entire quoted value with the URL Discord gave you (it starts with https://discord.com/api/webhooks/ — do not paste it on top of a prefix).
WEBHOOK_URL="paste-your-full-webhook-url"
curl -sS -o /dev/null -w "%{http_code}\
" -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"embeds\":[{\"title\":\"Failed payment (test)\",\"description\":\"Demo customer · 19.00 EUR\
Event: invoice.payment_failed\",\"color\":15158332,\"fields\":[{\"name\":\"Customer\",\"value\":\"cus_demo\",\"inline\":true},{\"name\":\"Email\",\"value\":\"demo@example.com\",\"inline\":true},{\"name\":\"Amount\",\"value\":\"19.00 EUR\",\"inline\":true}]}]}"Expected result: Discord returns 204 and the channel shows the test embed.
That proves the alert format and that the Incoming Webhook works. It does not prove Stripe delivered a signed event.
What you will map from Stripe
When a real invoice.payment_failed arrives, useful invoice fields are usually:
| Stripe field | In the embed |
|---|---|
customer | Customer |
customer_email | |
amount_due / currency | Amount (cents → units; 1900 + eur = 19.00 EUR) |
Invoice id | Invoice |
Event type | Event |
hosted_invoice_url | Title link (when present) |
Event detail: Stripe webhooks.
Wire Stripe
You already have the alert rendered in Discord. But Stripe cannot talk to Discord directly: something has to receive the event, check it really came from Stripe, and translate it into the message format.
For the other half you have three paths:
- No-code (Zapier, Make, etc.)
- Faster if you do not want to write or host code; same event → message idea.
- Your own HTTPS endpoint
- Stripe Dashboard → Developers → Webhooks → public URL that verifies `Stripe-Signature`, then POSTs to Discord.
- Stripe CLI locally
- Dev only: forwards events to a process **you** run on a port; without that process, `listen` does not complete the Discord 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 → embed (cents → units). POSTto your Discord Incoming Webhook.- Return
2xxto Stripe quickly. - Dedupe on
evt_…orinvoice.idif you do not want one message per retry.
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 Discord destination test and leave the Stripe glue for when you have somewhere to host the receiver. Raw event shape: Stripe webhooks.
Security
- Rotate the Incoming Webhook if the URL leaks.
- Keep Stripe test and live endpoints separate.
- Do not log the Discord URL or the signing secret.
- Plan for retries: Stripe may emit several
payment_failedevents for one invoice.
Common mistakes
| What you see | Likely cause | What to do |
|---|---|---|
| Discord 404 | Deleted webhook or stale URL | Create a new one and update the variable |
| Discord 401 / odd body | Truncated or mistyped URL | Copy again with no extra spaces |
curl OK, silent channel | Wrong server/channel | Confirm the webhook’s channel |
| Stripe “delivery failed” | Endpoint not 2xx or bad signature | Receiver logs; signing secret for that endpoint |
| Many embeds per customer | Retries without dedupe | Filter by invoice or space out alerts |
Alternatives
- Stripe email
- Zero code; less visible to the team.
- Slack Incoming Webhook
- Same event, different destination.
- Telegram bot
- Same event to a chat or group, with token and chat_id.