6 min read

How to Track Invoicing in Stripe

If you're sending invoices through Stripe, you need visibility into what's paid, what's overdue, and what's outstanding. Stripe's Invoices API and webhook system let you track this in real time, whether you're managing a handful of invoices or thousands.

Find Your Invoices in Stripe

Start with the Dashboard to understand where invoice data lives.

Navigate to the Invoices section

Log into your Stripe Dashboard and go to Invoicing > Invoices. You'll see all invoices with their status, customer, amount, and payment date. Filter by status, customer, or date range to find what you need.

Understand invoice statuses

Stripe invoices have five statuses: draft (not sent), open (sent, awaiting payment), paid (payment collected), void (cancelled), and uncollectible (failed after retries). Focus on open and uncollectible — those need action.

javascript
// Invoice statuses in Stripe
const invoiceStatuses = {
  draft: 'not yet sent',
  open: 'sent, waiting for payment',
  paid: 'payment received',
  void: 'cancelled',
  uncollectible: 'payment failed'
};
Reference for Stripe invoice statuses

Query Invoices via the API

To track invoicing at scale, use the Invoices API directly.

List invoices for a customer

Call stripe.invoices.list() with a customer filter to retrieve all invoices for that customer. Check the status and paid fields to see what's outstanding.

javascript
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const invoices = await stripe.invoices.list({
  customer: 'cus_12345',
  limit: 100
});

invoices.data.forEach(invoice => {
  console.log(`${invoice.id}: ${invoice.status}, Paid: ${invoice.paid}`);
});
Fetch all invoices for a customer

Filter invoices by status

Add a status filter to pull only the invoices that matter. For unpaid, use open. For failed payments, use uncollectible. You can also sort by date to find recent invoices.

javascript
// Get all open (unpaid) invoices
const openInvoices = await stripe.invoices.list({
  status: 'open',
  limit: 100
});

// Get paid invoices from last 30 days
const paidInvoices = await stripe.invoices.list({
  status: 'paid',
  created: {
    gte: Math.floor(Date.now() / 1000) - (30 * 24 * 60 * 60)
  }
});
Filter invoices by status and date range

Calculate unpaid and overdue totals

Sum the amount_due field across invoices to find total unpaid. Check the due_date field to identify overdue invoices. Remember: amounts are in cents, so divide by 100 for display.

javascript
const invoices = await stripe.invoices.list({ status: 'open' });

let totalUnpaid = 0;
let totalOverdue = 0;
const now = Math.floor(Date.now() / 1000);

invoices.data.forEach(invoice => {
  totalUnpaid += invoice.amount_due;
  if (invoice.due_date && invoice.due_date < now) {
    totalOverdue += invoice.amount_due;
  }
});

console.log(`Unpaid: $${(totalUnpaid / 100).toFixed(2)}`);
console.log(`Overdue: $${(totalOverdue / 100).toFixed(2)}`);
Sum unpaid and overdue invoice amounts

Monitor Changes with Webhooks

For real-time tracking, listen to invoice events as they happen.

Create a webhook endpoint in Stripe

Go to Developers > Webhooks in your Stripe Dashboard and create an endpoint pointing to your server. Subscribe to invoice.created, invoice.payment_succeeded, invoice.payment_failed, and invoice.marked_uncollectible events.

javascript
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();

const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

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

app.listen(3000, () => console.log('Listening for webhooks'));
Basic webhook endpoint handler

Handle invoice events

In your webhook handler, check the event.type and act on invoice changes. Log the invoice ID, customer, amount, and status to your own database for historical tracking.

javascript
if (event.type === 'invoice.payment_succeeded') {
  const invoice = event.data.object;
  console.log(`Invoice ${invoice.id} paid by ${invoice.customer}`);
  // Log to your database
}

if (event.type === 'invoice.payment_failed') {
  const invoice = event.data.object;
  console.log(`Invoice ${invoice.id} payment failed`);
  // Trigger a retry or send a reminder
}

if (event.type === 'invoice.marked_uncollectible') {
  const invoice = event.data.object;
  console.log(`Invoice ${invoice.id} marked uncollectible`);
  // Flag for review
}
Handle different invoice webhook events
Watch out: Stripe retries webhooks if your endpoint doesn't respond with a 200 status. Always return success immediately, even if your logging is async. This prevents duplicate processing.

Common Pitfalls

  • Forgetting Stripe amounts are in cents. Divide by 100 before displaying as dollars to users.
  • Counting draft invoices as revenue. Draft invoices aren't sent — filter by open, paid, or uncollectible only.
  • Not deduplicating webhook events. Stripe retries for 3 days; track by invoice ID and timestamp to avoid double-counting.
  • Assuming unpaid means overdue. Always check the due_date field — an unpaid invoice is only overdue if that date has passed.

Wrapping Up

You now have three ways to track your Stripe invoices: the Dashboard for quick checks, the API for programmatic queries, and webhooks for real-time updates. This setup lets you identify unpaid and overdue invoices, calculate total outstanding amounts, and trigger actions automatically. If you want to track this automatically across tools, Product Analyst can help.

Track these metrics automatically

Product Analyst connects to your stack and surfaces the insights that matter.

Try Product Analyst — Free