You're processing payments through Stripe, but pulling revenue reports feels manual and slow. Logging into the Dashboard each day doesn't scale, especially when you need metrics across multiple tools. Here's how to automate it.
Understand What Counts as Revenue
Stripe tracks revenue as successful charges minus refunds. Before you automate anything, know exactly what you're measuring.
Know the difference between charges and revenue
In Stripe, a charge is an attempted payment. A payment intent represents a payment that may succeed or fail. Revenue only counts when a charge is paid: true and refunded: false. Disputed transactions and failed payments never count as revenue, even if they appear in your Stripe account.
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
// Get a charge and check its revenue status
const charge = await stripe.charges.retrieve('ch_1234567890');
console.log(charge.paid); // true or false
console.log(charge.refunded); // true or false
console.log(charge.amount); // in cents, e.g., 10000 = $100Understand test vs. live mode
Stripe completely separates test and live data. Your test charges don't count toward live revenue. Always use the live secret key when reporting real revenue. If you query all charges, check the mode field to filter out test data.
amount field in Stripe is always in the smallest currency unit (cents for USD). Divide by 100 before displaying as currency.Set Up Real-Time Revenue Tracking with Webhooks
Webhooks let Stripe push revenue events to your system instantly. This is the best way to keep your revenue dashboard in sync without polling the API.
Create a webhook endpoint
Set up an HTTPS endpoint that receives POST requests from Stripe. It must validate the webhook signature, process the event, and return a 200 status within 5 seconds. Stripe retries failed deliveries for 3 days, so use event IDs as idempotency keys to avoid double-counting revenue.
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['stripe-signature'];
try {
const event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
// Always respond 200 immediately
res.json({received: true});
// Process asynchronously
handleWebhookEvent(event);
} catch (err) {
console.error('Webhook verification failed:', err.message);
res.status(400).send('Webhook error');
}
});
app.listen(3000, () => console.log('Webhook server running'));Register your webhook in Stripe
Go to Developers > Webhooks in your Stripe Dashboard. Click Add endpoint and paste your HTTPS URL. Stripe tests it immediately. Select the payment_intent.succeeded event, which fires when a customer's payment completes. Save and copy the signing secret into your .env as STRIPE_WEBHOOK_SECRET.
Log revenue when payments succeed
Listen for the payment_intent.succeeded event, extract the amount and customer info, and save it to your database. Always check amount_received to ensure the payment was actually captured—a payment intent can succeed but the charge might fail at settlement.
function handleWebhookEvent(event) {
if (event.type === 'payment_intent.succeeded') {
const paymentIntent = event.data.object;
const revenue = {
id: event.id, // Use as idempotency key
amount: paymentIntent.amount_received / 100,
currency: paymentIntent.currency.toUpperCase(),
customer_id: paymentIntent.customer,
timestamp: new Date(paymentIntent.created * 1000),
status: paymentIntent.status
};
saveRevenueRecord(revenue);
console.log(`Revenue logged: ${revenue.amount} ${revenue.currency}`);
}
}event.id as an idempotency key in your database to prevent logging the same revenue twice.Query Historical Revenue with the Charges API
Webhooks only track new events going forward. To backfill historical revenue or audit past data, use the Charges API with date filters.
Fetch charges within a date range
Use stripe.charges.list() with the created filter to get charges from a specific period. Pass gte and lte in Unix timestamps (seconds since epoch). Stripe returns up to 100 charges per request—loop through the results to get all charges.
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
// Get all charges from January 2024
const startDate = Math.floor(new Date('2024-01-01').getTime() / 1000);
const endDate = Math.floor(new Date('2024-01-31').getTime() / 1000);
const charges = await stripe.charges.list({
limit: 100,
created: {
gte: startDate,
lte: endDate
}
});
console.log(`Fetched ${charges.data.length} charges`);Calculate total revenue for the period
Loop through the charges and sum only the ones where paid: true and refunded: false. Subtract amount_refunded for partially refunded charges. This gives you net revenue.
let grossRevenue = 0;
let refundedAmount = 0;
charges.data.forEach(charge => {
if (charge.paid) {
grossRevenue += charge.amount;
}
if (charge.refunded) {
refundedAmount += charge.amount_refunded;
}
});
const netRevenue = (grossRevenue - refundedAmount) / 100;
console.log(`Net revenue: $${netRevenue}`);Common Pitfalls
- Forgetting to divide amounts by 100 — Stripe stores all amounts in the smallest currency unit (cents), not dollars
- Checking only charge.paid without checking charge.refunded — a charge can be marked paid but then partially or fully refunded later
- Using charge.created as the revenue date without understanding it's when the charge was initiated, not when it settled — use created for grouping but expect 1-2 day delays
- Not validating webhook signatures — accepting unsigned webhooks exposes you to fake revenue records and security issues
Wrapping Up
You now have a complete revenue reporting setup: real-time tracking via webhooks and historical data via the Charges API. This lets you monitor revenue without the Dashboard and integrate it into your tools. If you want to track revenue across multiple payment providers automatically, Product Analyst can help.