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:
- What did Stripe actually send? The Stripe dashboard shows events from their perspective, but not what your server received.
- 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 ininvoice.payment_failed- Money at riskcharge.refunded- Money out
Subscriptions:
customer.subscription.created- New subscribercustomer.subscription.updated- Plan changecustomer.subscription.deleted- Churn
Customers:
customer.created- New Stripe customercustomer.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.
