6 min read

How to Track Billing Portal in Stripe

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.

javascript
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/...
Create a billing portal session and get the session URL

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.

javascript
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()}`);
Tip: Include a 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.

javascript
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.

javascript
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' }
  );
}
Watch out: Webhook events retry for up to 3 days if your handler fails. Make sure your code is idempotent — processing the same event twice shouldn't duplicate your analytics data.

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.

javascript
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.

javascript
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);
Tip: Combine portal sessions with subscription update events (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_url parameter. 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.

Track these metrics automatically

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

Try Product Analyst — Free