Connecting Stripe webhooks to Slack alerts
- Don't route all Stripe events to Slack — you'll mute the channel in a week.
- Route only the events that require human action within 24 hours.
- Multi-brand operators need per-brand channels with a consolidated fraud/risk channel on top.
On this page
Stripe emits about 180 webhook events. If you pipe all of them to Slack, you've built a notification hose that nobody reads. The right pattern is: few events, specific channels, real action triggers. Here's how we set this up for operators running 5-20 brands.
1. Which events deserve Slack
Tier 1 (always route): charge.dispute.created, radar.early_fraud_warning.created, payout.failed, payout.paid (for ops visibility), account.updated (reserve or capability changes), charge.refunded (above threshold).
Tier 2 (route with filters): invoice.payment_failed (subscription operators only), customer.subscription.deleted (churn signal), charge.failed with network_reason 4700/do_not_honor concentration.
Tier 3 (don't route): charge.succeeded (noise), customer.created (noise), everything *.created that doesn't require intervention.
2. Architecture pattern
Stripe → webhook endpoint (your server) → event filter → Slack Incoming Webhook or Slack API. Do not connect Stripe's webhook directly to a Slack webhook via Zapier/Make without a filter; you'll flood the channel and start ignoring it.
3. The filter layer
A tiny Node/Python/Go service that takes the Stripe webhook, validates signature, checks event type against an allowlist, formats the message, and posts to the right Slack channel. ~200 lines of code. The value is in the filter rules, not the tech.
4. Signature validation
Always verify the Stripe-Signature header before acting on a webhook. Anyone knowing your webhook URL can POST forged events; signature check prevents that. Stripe's libraries do this in one line. See webhook reliability.
5. Per-brand channel structure
For multi-brand, create #payments-brand-a, #payments-brand-b, etc. — plus a consolidated #payments-critical that fires on disputes, reserve changes, and payout failures across all brands. Ops team watches the brand channels during business hours; the critical channel is on-call 24/7.
6. Message formatting
Each alert should contain: event type, brand/account, amount, customer email (redacted), link to Stripe dashboard entry, recommended action. Example for dispute:
:rotating_light: Dispute opened — brand A Charge: $148.00 • ch_1QxyzABC... Reason: fraudulent Customer: j***@example.com Evidence due: Apr 28 Action: Build rebuttal in dispute queue
The recommended action turns the alert into a task, not a notification.
7. Dispute-specific alert
Dispute webhooks should also pull the cardholder BIN, the risk score, and the reason code. If you can include the matching order in your fulfillment system (via customer email lookup), do that. A dispute alert with zero context requires 3-5 clicks to act on; an enriched alert requires one.
8. Reserve and capability changes
account.updated fires when Stripe changes your reserve, your processing capabilities, or your risk status. These need immediate eyes. Alert format should diff the old vs new value explicitly: "Reserve changed from 5% rolling 90d to 10% rolling 120d." Operators miss reserve changes because the event is buried in a noisy feed.
9. Payout alerts
payout.paid in the finance channel with the expected reconciliation amount. payout.failed in the critical channel with the bank error code. Payout failures on Friday afternoon are a weekend emergency unless someone sees the alert.
10. Dunning alerts
invoice.payment_failed for subscriptions should alert only if the customer has already failed a prior attempt — one-time failures are normal and handled by Stripe's Smart Retries. Two-in-a-row is a dunning churn risk and deserves CS attention.
11. Cross-brand alert consolidation
Operators on single-Stripe accounts get one event firehose. Operators on multi-brand with separate Stripe accounts get N firehoses. Consolidating requires either (a) a fan-in service that subscribes to all N accounts and publishes to Slack, or (b) an orchestration layer that emits unified events across all brands. The second is what we build at multi-flow.
12. Slack rate limits
Slack rate-limits incoming webhooks at 1 message per second per channel. High-volume operators will hit this during peak. Batch non-critical alerts (e.g., all charges over $500 from the last 5 minutes in one message) rather than one-per-charge.
13. On-call routing
Layer PagerDuty/Opsgenie on top of the critical channel — dispute opened outside business hours + amount > $X = page the on-call. Dispute opened during business hours = Slack only. This split is where event routing earns its keep.
14. What not to alert on
- Every successful charge (noise)
- Every refund below a threshold (noise; review in weekly report instead)
- Every webhook retry (handled by your retry logic, not Slack)
- Customer email updates (noise)
- Test mode events (route to a test channel or drop entirely)
Sample config, TypeScript
const CRITICAL = new Set([
"charge.dispute.created",
"radar.early_fraud_warning.created",
"payout.failed",
"account.updated",
]);
const PER_BRAND = new Set([
"payout.paid",
"charge.refunded",
"invoice.payment_failed",
]);
function route(event) {
if (CRITICAL.has(event.type)) return "#payments-critical";
if (PER_BRAND.has(event.type)) return brandChannel(event);
return null; // drop
}Next steps
For single-brand operators, this setup takes an afternoon. For multi-brand, the fan-in + filter layer is 1-2 weeks of engineering or use an orchestration layer that ships it. See pricing, Stripe comparison, or apply for a fit check.