
Stripe Push Notifications: Know the Moment You Get Paid
Get an instant push notification for every Stripe payment, failed charge, or dispute — paste one webhook URL into Stripe and you're done. No code required (but the API route is still there when you want custom logic).
Stripe Push Notifications: Know the Moment You Get Paid
There's something deeply satisfying about getting a push notification the moment revenue hits your Stripe account. But Stripe's own email notifications are delayed, and the Stripe app's alerts are limited to what Stripe decides to expose.
FlowAlert connects to Stripe with a native webhook — you paste a FlowAlert URL into Stripe, paste Stripe's signing secret into FlowAlert, and every payment event that matters lands on your phone, signature-verified, routed to the right person, with acknowledgement and escalation when it's something like a dispute.
That's the whole setup. If you'd rather write your own handler — custom event keys, your own filtering, an MRR bot — the API route still works, and it's covered at the end.
The no-code way: FlowAlert's native Stripe webhook
Step 1: Create a Stripe source in FlowAlert
In your FlowAlert dashboard go to Setup → Integrations, click Connect, choose Stripe, and give it a name. FlowAlert generates a unique receiver URL:
https://api.flowalert.io/v1/hooks/stripe/whk_xxxxxxxxxxxxxxxx
Copy it — you'll paste it into Stripe next.
Step 2: Add the webhook endpoint in Stripe
- In the Stripe Dashboard, go to Developers → Webhooks
- Click Add endpoint
- Paste your FlowAlert receiver URL as the Endpoint URL
- Under Select events, add the events you want to route (table below)
- Click Add endpoint
One thing worth knowing: Stripe only delivers the event types the endpoint is subscribed to. That includes test events — if you plan to use the dashboard's Send test webhook or stripe trigger, add the event you'll test with to the selected events, or nothing will arrive.
Step 3: Paste the signing secret back into FlowAlert
On the endpoint's page, Stripe shows a Signing secret starting with whsec_. Click Reveal, copy it, and paste it into your Stripe source under Setup → Integrations, then Save.
The signing secret is how FlowAlert proves each request genuinely came from Stripe. Until it's set, the source won't accept events.
That's it — three pastes, no server, no code.
What arrives, out of the box
FlowAlert understands these Stripe events and maps them to routable keys:
| Stripe event | FlowAlert event key | Priority |
|---|---|---|
invoice.payment_failed | stripe.payment_failed | HIGH |
charge.failed | stripe.payment_failed | HIGH |
payment_intent.payment_failed | stripe.payment_failed | HIGH |
charge.dispute.created | stripe.dispute_created | URGENT |
checkout.session.completed | stripe.payment_succeeded | NORMAL |
payment_intent.succeeded | stripe.payment_succeeded | NORMAL |
invoice.payment_succeeded | stripe.invoice_paid | NORMAL |
customer.subscription.created | stripe.subscription_created | NORMAL |
customer.subscription.trial_will_end | stripe.subscription_trial_ending | HIGH |
customer.subscription.deleted | stripe.subscription_canceled | NORMAL |
Other event types are accepted and acknowledged (Stripe shows a ✓) but won't create an alert.
Pick one event per payment flow. Several Stripe events deliberately map to the same FlowAlert key — subscribe to the one that matches how you take payments, or a single payment fires multiple alerts:
- Successful payments →
checkout.session.completedif you use Stripe Checkout, orpayment_intent.succeededif you use the Payment Element / Payment Intents API. - Failed payments →
invoice.payment_failedfor Billing/subscriptions, orcharge.failed/payment_intent.payment_failedfor one-off charges. - Subscription renewals are their own thing:
invoice.payment_succeeded→stripe.invoice_paid.
Every alert carries a small set of normalized fields you can use in routing conditions: data.amount (major units — 49.99, not cents), data.currency (USD), data.customer_email, and data.reason (the human-readable failure or dispute reason). Stripe retries failed deliveries, and FlowAlert de-duplicates by Stripe's event id — a redelivered event never becomes a second alert.
Route the events to the right people
In Setup → Routing Rules, create a rule per key you care about:
| Event key | Recipient | Priority | Requires Ack |
|---|---|---|---|
stripe.payment_succeeded | Founder | NORMAL | No |
stripe.subscription_created | Founder + Sales | NORMAL | No |
stripe.payment_failed | Billing / Support | HIGH | Yes |
stripe.subscription_canceled | Founder + CS | NORMAL | No |
stripe.dispute_created | Finance lead | URGENT | Yes |
For rules with acknowledgement required, FlowAlert keeps alerting until someone taps Acknowledge — and escalates to the next person if they don't. Disputes have a response deadline; this is exactly what escalation is for.
You can also add conditions — for example, route disputes over $500 to a different person with a condition on data.amount.
Test it
On the endpoint page in Stripe, use Send test webhook (the default test is payment_intent.succeeded — make sure it's one of your selected events), then check Dashboard → Alerts in FlowAlert. Once a routing rule exists for the key, the same event lands on a phone in a couple of seconds.
The custom way: your own webhook handler
The native integration covers the common events with sensible keys. Build your own handler instead when you want your own event keys, custom filtering, or alerts Stripe doesn't emit directly (like a daily MRR report).
You'll need a public HTTPS endpoint that verifies Stripe's signature and forwards what you care about to FlowAlert's REST API:
1const express = require('express');
2const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
3
4const app = express();
5
6async function flowalert(event, title, body, priority = 'NORMAL', data = {}) {
7 await fetch('https://api.flowalert.io/v1/events', {
8 method: 'POST',
9 headers: {
10 'Content-Type': 'application/json',
11 'X-Api-Key': process.env.FLOWALERT_API_KEY,
12 },
13 body: JSON.stringify({ event, title, body, priority, data }),
14 });
15}
16
17app.post(
18 '/webhook/stripe',
19 express.raw({ type: 'application/json' }),
20 async (req, res) => {
21 let event;
22 try {
23 event = stripe.webhooks.constructEvent(
24 req.body,
25 req.headers['stripe-signature'],
26 process.env.STRIPE_WEBHOOK_SECRET
27 );
28 } catch (err) {
29 return res.status(400).send('Webhook signature verification failed');
30 }
31
32 const obj = event.data.object;
33 if (event.type === 'payment_intent.succeeded') {
34 const amount = (obj.amount / 100).toFixed(2);
35 await flowalert(
36 'billing.payment_succeeded', // your key — name it whatever you route on
37 `💰 Payment — ${obj.currency.toUpperCase()} ${amount}`,
38 `Received from ${obj.receipt_email ?? 'unknown customer'}`,
39 'HIGH',
40 { paymentIntentId: obj.id }
41 );
42 }
43 res.json({ received: true });
44 }
45);
46
47app.listen(3000);The event keys here are yours to define — the key you send just has to match the key on your routing rule. During development, forward webhooks to localhost with stripe listen --forward-to localhost:3000/webhook/stripe and fire test events with stripe trigger payment_intent.succeeded.
Bonus: a daily MRR push
Alerts don't have to come from webhooks — anything that can call the API can push. A scheduled job that sums your active subscriptions and sends one LOW-priority note at 9am:
1async function sendMrrReport() {
2 const subscriptions = await stripe.subscriptions.list({ status: 'active', limit: 100 });
3 const mrr = subscriptions.data.reduce((sum, sub) => {
4 return sum + sub.items.data.reduce((s, item) => {
5 const amount = item.price.unit_amount ?? 0;
6 const interval = item.price.recurring?.interval;
7 return s + (interval === 'year' ? Math.round(amount / 12) : amount);
8 }, 0);
9 }, 0);
10
11 await flowalert('stripe.mrr_daily', `📊 Daily MRR Report`, `Current MRR: $${(mrr / 100).toFixed(0)}`, 'LOW');
12}Summary
For payment alerts, the native integration is the right default: three pastes, signature-verified, de-duplicated, and every failed charge or dispute reaches a person who has to acknowledge it. Reach for a custom handler only when you want your own keys or logic on top.


