Your customers are managing their subscriptions in the Stripe Billing Portal, but you probably don't know when they're accessing it, what they're changing, or if they're getting stuck. Tracking portal activity gives you visibility into self-service behavior that directly impacts churn and revenue. Here's how to capture that data in Stripe.
Create Billing Portal Sessions and Track Access
The first step is creating portal sessions that you can track. Each session is timestamped and linked to a specific customer, so you know exactly who accessed the portal and when.
Enable Billing Portal in your Stripe account
Go to Settings > Billing Portal in your Stripe dashboard. Toggle Enable Billing Portal and configure what features customers can manage (subscriptions, payment methods, tax IDs, etc.). This is a one-time setup — once enabled, you can create sessions programmatically.
Create a billing portal session with the Stripe Node SDK
When a customer clicks 'Manage Billing' in your app, create a portal session server-side. The SDK returns a session URL that redirects the customer to the hosted portal.
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const portalSession = await stripe.billingPortal.sessions.create({
customer: 'cus_ABC123',
return_url: 'https://example.com/account',
});
console.log(portalSession.url);
// Redirect customer to: https://billing.stripe.com/session/...Capture the session creation timestamp
Store the created timestamp from the portal session in your database alongside the customer ID. This marks when the customer started using the portal.
await db.insert('portal_sessions', {
customer_id: 'cus_ABC123',
session_id: portalSession.id,
created_at: new Date(portalSession.created * 1000),
return_url: portalSession.return_url,
});
console.log(`Portal accessed at ${new Date(portalSession.created * 1000).toISOString()}`);return_url parameter so customers land back in your app after exiting the portal. This closes the loop and lets you track the entire billing journey.Monitor Portal Activity with Webhooks
Stripe fires webhook events when customers interact with the billing portal. Hook into these events to log activity, trigger notifications, or update your analytics.
Configure webhook events in your Stripe account
Go to Developers > Webhooks in the Stripe dashboard. Create an endpoint pointing to your webhook handler (e.g., https://api.example.com/webhooks/stripe). Select the billing_portal.session.created and billing_portal.session.expired events.
Listen for billing portal session events
In your webhook handler, capture billing_portal.session.created to know when a customer enters the portal. The event includes the customer ID, session ID, and creation timestamp.
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/webhooks/stripe', express.raw({type: 'application/json'}), async (req, res) => {
const sig = req.headers['stripe-signature'];
const event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
if (event.type === 'billing_portal.session.created') {
const session = event.data.object;
console.log(`Customer ${session.customer} accessed portal at ${new Date(session.created * 1000).toISOString()}`);
await analytics.track('billing_portal_accessed', {
customer_id: session.customer,
session_id: session.id,
timestamp: new Date(session.created * 1000),
});
}
res.json({received: true});
});Track session expiry to identify abandonment
Stripe sends a billing_portal.session.expired event when the session times out (typically after 24 hours of inactivity). Track this to identify customers who started but didn't complete their billing tasks.
if (event.type === 'billing_portal.session.expired') {
const session = event.data.object;
console.log(`Portal session expired for customer ${session.customer}`);
await db.update('portal_sessions',
{ session_id: session.id },
{ expired_at: new Date(), status: 'expired' }
);
}Query Portal Activity via the Stripe API
Beyond webhooks, you can query the Stripe API directly to pull billing portal session history for reporting and analysis.
List billing portal sessions for a customer
Use the billingPortal.sessions.list() method to fetch all portal sessions for a specific customer. This gives you a complete history of portal access without relying on webhook delivery.
const sessions = await stripe.billingPortal.sessions.list({
customer: 'cus_ABC123',
limit: 100,
});
sessions.data.forEach(session => {
console.log(`Session: ${session.id}, Created: ${new Date(session.created * 1000).toISOString()}`);
});Analyze portal access trends
Aggregate portal sessions by time period to see patterns. Compare weekly portal access to subscription churn — if portal visits drop before churn spikes, you've found a friction point.
const thirtyDaysAgo = Math.floor((Date.now() - 30 * 24 * 60 * 60 * 1000) / 1000);
const recentSessions = await stripe.billingPortal.sessions.list({
created: { gte: thirtyDaysAgo },
limit: 100,
});
const sessionsByDay = {};
recentSessions.data.forEach(session => {
const date = new Date(session.created * 1000).toISOString().split('T')[0];
sessionsByDay[date] = (sessionsByDay[date] || 0) + 1;
});
console.log('Portal sessions per day:', sessionsByDay);customer.subscription.updated, charge.succeeded) to map what billing changes customers made during each portal visit.Common Pitfalls
- Assuming no events means no activity. Webhook delivery can be delayed or fail silently. Always cross-check with the API's
list()method to verify portal access. - Confusing session count with unique customers. Billing portal sessions expire after 24 hours, so repeat customers create multiple sessions. You need to deduplicate by customer ID to count actual users.
- Skipping the
return_urlparameter. Without it, customers are stuck on Stripe's domain after closing the portal, which breaks your UX and makes follow-up tracking impossible. - Ignoring what customers *changed* in the portal. Stripe only tells you when the portal was accessed, not what subscription or payment method updates they made. Pair session events with subscription webhooks for the full picture.
Wrapping Up
You now have full visibility into when and how often customers access your Stripe Billing Portal. Use this data to catch churn signals early — a sudden drop in portal visits often precedes cancellations. If you want to track this automatically across tools, Product Analyst can help.