Most SaaS founders are in Slack all day. Their product dashboard? Not so much.
That gap costs you real money. A new enterprise signup sits ungreeted for hours. A failed payment gets noticed three days later. A support ticket goes stale while the user churns.
The fix is simple: push the events that matter directly to Slack, and you react in seconds instead of days.
Not everything deserves a Slack message. Alert on too much and people start ignoring the channel. A useful rule of thumb:
Slack lets you create an "incoming webhook" URL for any channel. You POST a JSON body to that URL and a message appears in the channel. No OAuth, no Slack SDK, just a fetch call.
SLACK_WEBHOOK_URL in your environment variables.The call belongs in your service layer, not in a route. Here is the helper you need:
// lib/slack.ts
export async function sendSlackAlert(text: string): Promise<void> {
const url = process.env.SLACK_WEBHOOK_URL;
if (!url) return; // no-op in local dev
await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text }),
});
}
Returning silently when the URL is missing means alerts do not crash your app in local dev or test environments -- they just do nothing.
Now call it wherever the event happens:
// After a successful signup in user.service.ts
await sendSlackAlert(`New signup: ${user.email} (${plan} plan)`);
// After detecting a failed Stripe payment
await sendSlackAlert(`Payment failed: ${user.email} -- card declined`);
Place the sendSlackAlert call after the database write succeeds, never inside a transaction. If Slack is slow or down, you do not want it rolling back your user record.
Slack accepts richer blocks formatting, but plain text with emoji is readable enough and much easier to maintain. A useful alert format has three parts: what happened, who it involves, and one piece of context:
New signup: alice@example.com (Pro plan) -- referred by google
Payment failed: bob@startup.com -- retries: 2/4
Support ticket #81: carol@corp.com -- "Export not working"
Anything more and people start ignoring the channel.
If you are already handling Stripe webhooks -- which any SaaS with payments should -- adding Slack alerts takes two lines per event:
case "customer.subscription.created":
const sub = event.data.object as Stripe.Subscription;
await sendSlackAlert(`New subscriber: ${sub.customer} -- plan ${sub.items.data[0].price.id}`);
break;
case "invoice.payment_failed":
const inv = event.data.object as Stripe.Invoice;
await sendSlackAlert(`Payment failed: customer ${inv.customer}`);
break;
Stripe sends these events every time they happen -- your Slack channel becomes a live stream of your business without any polling.
One channel is fine to start. Once volume grows, split by urgency:
#saas-revenue -- paid signups, upgrades, churns
#saas-alerts -- failed payments, errors, quota hits
#saas-signups -- all signups including free (can be noisy)
Different webhook URLs, different environment variables. The same sendSlackAlert helper works -- just parameterize the URL.
If you are building on this Next.js SaaS boilerplate, your service layer is already structured for this. Add the lib/slack.ts helper, set one environment variable, and drop two lines into the services that fire the events you care about. No new dependencies, no new infrastructure.
Related reads: how to handle Stripe failed payments and dunning, and error monitoring before your users notice.
Start with new paid signups and failed payments -- that is the 20% of events that drives 80% of the decisions you will make in the next 30 days. Set up the webhook this afternoon, get one real alert, and see how fast you start making better decisions.
Get started with the Next.js SaaS boilerplate and have Slack alerts running before your next deploy.