Guides / Payments

How to Monitor Failed Stripe Payments

4 min read·Payments

Why Failed Payments Kill SaaS

A payment fails. The user doesn't notice. Their subscription lapses. You find out when they tweet about their account being locked.

With LogIt, you get notified the second invoice.payment_failed fires — giving you a window to reach out before the churn is permanent.

Setup

bash
npm install logit stripe

Handler

ts
// app/api/webhooks/stripe/route.ts
import Stripe from "stripe";
import { init } from "logit";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const logit = init({ token: process.env.LOGIT_API_KEY! });

export async function POST(req: Request) {
  const body = await req.text();
  const sig = req.headers.get("stripe-signature")!;
  const event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);

  switch (event.type) {
    case "invoice.payment_failed": {
      const invoice = event.data.object;
      await logit.now("errors", {
        event: "Payment failed ❌",
        description: `${invoice.customer_email ?? "unknown"} — attempt ${invoice.attempt_count ?? 1}`,
        icon: "❌",
        notify: true,
        tags: { attempt: String(invoice.attempt_count ?? 1) },
        metadata: {
          invoiceId: invoice.id,
          email: invoice.customer_email,
          customerId: invoice.customer,
          nextAttempt: invoice.next_payment_attempt,
        },
      });
      break;
    }

    case "charge.failed": {
      const charge = event.data.object;
      await logit.now("errors", {
        event: "Charge failed",
        description: `${charge.billing_details.email ?? "unknown"}${charge.failure_message ?? "declined"}`,
        icon: "❌",
        notify: true,
        tags: { reason: charge.failure_code ?? "unknown" },
        metadata: {
          chargeId: charge.id,
          email: charge.billing_details.email,
          failureMessage: charge.failure_message,
        },
      });
      break;
    }

    case "customer.subscription.deleted": {
      const sub = event.data.object;
      await logit.now("churn", {
        event: "Subscription cancelled",
        description: `Customer ${sub.customer}`,
        icon: "😢",
        notify: true,
        metadata: { subscriptionId: sub.id, reason: sub.cancellation_details?.reason },
      });
      break;
    }
  }

  return new Response("ok");
}

Subscribe to these events in Stripe

  • invoice.payment_failed
  • charge.failed
  • customer.subscription.deleted

Tip

Use the next_payment_attempt field from the invoice to know when Stripe will retry — and reach out to the customer before the next attempt fails too.

Try LogIt free

7-day trial. No credit card required.

Start free