All posts

Monitoring Stripe Webhooks with Quicklog

A practical guide to tracking Stripe webhook events in real-time. Debug payment issues faster and never miss a failed charge.

Stripe webhooks are essential for any SaaS with subscriptions. They tell you when payments succeed, when they fail, when customers cancel, and dozens of other important events. But debugging webhook issues is notoriously painful.

Here's how to set up real-time Stripe webhook monitoring with Quicklog.

Why monitor webhooks?

Stripe sends webhooks to your server, and your server processes them. When something goes wrong, you have two blind spots:

  1. What did Stripe actually send? The Stripe dashboard shows events from their perspective, but not what your server received.
  2. How did your server respond? Did you process the event correctly? Did you update the user's subscription? Did you send the email?

Without visibility into both sides, debugging payment issues means digging through logs, comparing timestamps, and guessing.

Basic webhook tracking

Start by logging every webhook you receive. In your webhook handler:

app.post('/webhooks/stripe', async (req, res) => {
  const sig = req.headers['stripe-signature']
  const event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret)

  // Log to Quicklog
  await quicklog.track({
    channel: 'stripe',
    event: event.type,
    description: describeEvent(event),
    metadata: {
      stripeEventId: event.id,
      ...event.data.object
    }
  })

  // Process the event
  await handleStripeEvent(event)

  res.json({ received: true })
})

Now every Stripe event appears in your Quicklog dashboard with its full payload.

Make descriptions useful

The event type alone (invoice.payment_succeeded) doesn't tell you much at a glance. Write a helper function that creates human-readable descriptions:

function describeEvent(event) {
  const obj = event.data.object

  switch (event.type) {
    case 'customer.subscription.created':
      return `New subscription: ${obj.customer_email} on ${obj.plan?.nickname || 'unknown plan'}`

    case 'customer.subscription.deleted':
      return `Subscription cancelled: ${obj.customer_email}`

    case 'invoice.payment_succeeded':
      return `Payment received: $${(obj.amount_paid / 100).toFixed(2)} from ${obj.customer_email}`

    case 'invoice.payment_failed':
      return `Payment failed: ${obj.customer_email} - ${obj.last_payment_error?.message || 'unknown error'}`

    default:
      return `Stripe: ${event.type}`
  }
}

Now your feed shows "Payment received: $49.00 from sarah@startup.com" instead of just "invoice.payment_succeeded".

Connect to users

If you have the user's information, include it:

await quicklog.track({
  channel: 'stripe',
  event: event.type,
  description: describeEvent(event),
  user: {
    email: event.data.object.customer_email,
    // Map Stripe customer ID to your user ID if possible
    id: await getUserIdFromStripeCustomer(event.data.object.customer)
  },
  metadata: event.data.object
})

This lets you see all Stripe events for a specific user, which is invaluable when debugging customer issues.

Track your responses too

Knowing what Stripe sent is only half the picture. Track what your system did in response:

async function handleSubscriptionCreated(subscription) {
  // Enable features for the user
  await enableProFeatures(subscription.customer)

  // Log what you did
  await quicklog.track({
    channel: 'stripe',
    event: 'subscription.provisioned',
    description: `Enabled Pro features for ${subscription.customer_email}`,
    user: { email: subscription.customer_email },
    metadata: {
      stripeEventId: subscription.id,
      features: ['unlimited_projects', 'priority_support', 'api_access']
    }
  })

  // Send welcome email
  await sendWelcomeEmail(subscription.customer_email)

  await quicklog.track({
    channel: 'stripe',
    event: 'welcome_email.sent',
    description: `Sent Pro welcome email to ${subscription.customer_email}`,
    user: { email: subscription.customer_email }
  })
}

Now you can trace the complete flow: Stripe event received → features enabled → email sent.

Alert on failures

Failed payments deserve immediate attention. You might lose a customer if you don't act quickly.

Set up notifications for payment failures:

if (event.type === 'invoice.payment_failed') {
  await quicklog.track({
    channel: 'alerts', // A channel you have notifications enabled for
    event: 'payment.failed',
    description: `⚠️ Payment failed: ${obj.customer_email}`,
    notify: true,
    metadata: {
      amount: obj.amount_due,
      attempt: obj.attempt_count,
      nextRetry: obj.next_payment_attempt,
      error: obj.last_payment_error?.message
    }
  })
}

Common events to watch

Focus on these Stripe events:

Revenue:

  • invoice.payment_succeeded - Money in
  • invoice.payment_failed - Money at risk
  • charge.refunded - Money out

Subscriptions:

  • customer.subscription.created - New subscriber
  • customer.subscription.updated - Plan change
  • customer.subscription.deleted - Churn

Customers:

  • customer.created - New Stripe customer
  • customer.updated - Updated payment method

Debugging with full payloads

When something goes wrong, click on any event to see the full payload. You'll see exactly what Stripe sent, including:

  • Customer details
  • Payment method information
  • Error messages and codes
  • Timestamps

This is often enough to diagnose the issue without diving into logs or contacting Stripe support.

Getting started

Add webhook tracking today and start building a history of Stripe events. Even if you don't have an immediate problem to debug, having the data available when issues do occur is invaluable.

The setup takes about 10 minutes, and the visibility it provides will save you hours the next time a customer asks "why was I charged twice?"

Ready to try Quicklog?

Start tracking your product events in minutes.