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.

Flow
  1. payment_failed event
  2. Your code maps it
  3. 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

  1. Open the channel (prefer a dedicated one, e.g. #billing-failures).
  2. Channel gear → IntegrationsWebhooksNew Webhook.
  3. Clear name, e.g. Stripe failures.
  4. 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).

Language: bash
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 fieldIn the embed
customerCustomer
customer_emailEmail
amount_due / currencyAmount (cents → units; 1900 + eur = 19.00 EUR)
Invoice idInvoice
Event typeEvent
hosted_invoice_urlTitle 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:

  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 → embed (cents → units).
  4. POST to your Discord Incoming Webhook.
  5. Return 2xx to Stripe quickly.
  6. Dedupe on evt_… or invoice.id if you do not want one message per retry.

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 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_failed events for one invoice.

Common mistakes

What you seeLikely causeWhat to do
Discord 404Deleted webhook or stale URLCreate a new one and update the variable
Discord 401 / odd bodyTruncated or mistyped URLCopy again with no extra spaces
curl OK, silent channelWrong server/channelConfirm the webhook’s channel
Stripe “delivery failed”Endpoint not 2xx or bad signatureReceiver logs; signing secret for that endpoint
Many embeds per customerRetries without dedupeFilter 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.

Resources