Customer Portal
Create a Stripe customer portal session for managing billing.
Customer Portal
Creates a Stripe customer portal session that allows users to manage their subscription, payment methods, and billing history.
Endpoint
POST /api/billing/portalAuthentication
Required. Bearer token in Authorization header.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
returnUrl | string | Yes | URL to return to after portal session |
configuration | string | No | Stripe portal configuration ID (uses default if not provided) |
Example Request
curl -X POST https://api.example.com/api/billing/portal \
-H "Authorization: Bearer <session_token>" \
-H "Content-Type: application/json" \
-d '{
"returnUrl": "https://app.example.com/dashboard/settings/billing"
}'TypeScript Example
const response = await fetch('/api/billing/portal', {
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
returnUrl: `${window.location.origin}/dashboard/settings/billing`
})
});
const { data } = await response.json();
// Redirect to Stripe customer portal
window.location.href = data.url;Response
Success Response (200)
{
"success": true,
"data": {
"url": "https://billing.stripe.com/session/_1234567890abcdef"
}
}| Field | Type | Description |
|---|---|---|
url | string | Stripe customer portal URL |
Portal Features
The customer portal allows users to:
- Update payment methods - Add, remove, or update credit cards
- View billing history - See past invoices and payment receipts
- Download invoices - Get PDF copies of invoices
- Update subscription - Upgrade, downgrade, or cancel plans
- Update billing info - Change billing address and contact details
Error Responses
No Customer Record (404)
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "No billing customer record found"
}
}Unauthorized (401)
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
}React Component Example
import { useState } from 'react';
function ManageBillingButton() {
const [loading, setLoading] = useState(false);
const openPortal = async () => {
setLoading(true);
try {
const response = await fetch('/api/billing/portal', {
method: 'POST',
headers: {
'Authorization': `Bearer ${getToken()}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
returnUrl: window.location.href
})
});
const { data, error } = await response.json();
if (error) {
throw new Error(error.message);
}
// Redirect to Stripe portal
window.location.href = data.url;
} catch (err) {
console.error('Failed to open portal:', err);
alert('Failed to open billing portal. Please try again.');
} finally {
setLoading(false);
}
};
return (
<button
onClick={openPortal}
disabled={loading}
className="btn btn-primary"
>
{loading ? 'Loading...' : 'Manage Billing'}
</button>
);
}Settings Page Integration
function BillingSettings() {
const { subscription } = useSubscription();
return (
<div className="billing-settings">
<h2>Billing</h2>
<section className="current-plan">
<h3>Current Plan</h3>
<p>{subscription?.plan?.name || 'Free'}</p>
{subscription?.status === 'active' && (
<p>Renews on {new Date(subscription.currentPeriodEnd).toLocaleDateString()}</p>
)}
</section>
<section className="actions">
{subscription?.status === 'active' ? (
<>
<ManageBillingButton />
<p className="text-sm text-gray-500">
Manage payment methods, view invoices, and update your subscription.
</p>
</>
) : (
<UpgradeButton />
)}
</section>
</div>
);
}Flow Diagram
┌─────────────┐ POST /portal ┌─────────────┐
│ Client │ ───────────────────────>│ Server │
│ Settings │ │ │
│ Page │ <───────────────────────│ │
│ │ { url } │ │
└─────────────┘ └─────────────┘
│
│ Redirect to Stripe
▼
┌─────────────┐
│ Stripe │
│ Portal │
│ (Embedded) │
└─────────────┘
│
│ Return to returnUrl
▼
┌─────────────┐
│ Client │
│ Settings │
│ Page │
└─────────────┘Notes
- Portal sessions are valid for 1 hour
- Users without an active subscription can still access the portal to view past invoices
- The portal URL is single-use; generate a new one for each visit
- Custom branding can be configured in the Stripe Dashboard
- Portal configuration controls which features are available (cancellations, plan switches, etc.)
Webhook Events
When users make changes in the portal, these webhooks are triggered:
| Event | Description |
|---|---|
customer.subscription.updated | Plan changed or subscription modified |
customer.subscription.deleted | Subscription cancelled |
customer.updated | Billing details updated |
payment_method.attached | New payment method added |
payment_method.detached | Payment method removed |