Customer Portal
Let customers see and manage their subscriptions and billing history through a hosted portal.
Overview
The Customer Portal is a hosted page where your customers can self-manage their subscriptions without you building custom UI. Create a portal session to generate a short-lived URL, then redirect (or open) the customer to it.
Create a Portal Session
/api/v1/portal-sessionsAuth: Server key required
Request Body
{
"customerId": "cus_xyz789",
"returnUrl": "https://yoursite.com/account",
"configuration": {
"allowCancel": true,
"allowPause": true,
"allowPaymentMethodUpdate": true,
"branding": {
"primaryColor": "#4F46E5",
"logoUrl": "https://yoursite.com/logo.png",
"companyName": "Acme Inc."
}
}
}Response
{
"id": "ps_abc123",
"object": "portal_session",
"url": "https://portal.quickpay.com/session/ps_abc123_tok_xyz",
"customerId": "cus_xyz789",
"returnUrl": "https://yoursite.com/account",
"expiresAt": "2025-01-15T11:30:00Z",
"createdAt": "2025-01-15T10:30:00Z"
}Example (Node.js)
import { QuickPay } from '@quickpay/node';
const qp = new QuickPay('sk_live_your_server_key');
app.post('/api/create-portal-session', async (req, res) => {
const { customerId } = req.body;
const session = await qp.portalSessions.create({
customerId,
returnUrl: 'https://yoursite.com/account',
configuration: {
allowCancel: true,
allowPause: true,
allowPaymentMethodUpdate: true,
branding: {
primaryColor: '#4F46E5',
logoUrl: 'https://yoursite.com/logo.png',
companyName: 'Acme Inc.',
},
},
});
res.json({ url: session.url });
});Portal URL Format
Portal session URLs follow this format:
https://portal.quickpay.com/session/{session_id}_{token}Each URL is single-use and expires after 1 hour. If the customer needs to access the portal again, create a new portal session.
Security
Portal session URLs grant full access to the customer's subscription management. Never expose the URL in publicly accessible logs or share it with unauthorized parties. Always generate a fresh session URL when the customer requests access.
Portal Capabilities
The portal provides the following self-service capabilities to your customers:
View Subscriptions
Customers can see all their active, paused, and cancelled subscriptions including plan details, billing amounts, and next billing date.
Update Payment Method — not available yet
The portal lists saved payment methods, but changing the card used for renewals is not available yet; the endpoint returns 501 not_implemented.
Cancel / Pause / Resume
Depending on your configuration, customers can cancel their subscription (immediately or at period end), pause it for a duration, or resume a paused subscription.
Billing History
Customers can view past invoices and payment receipts, including downloadable PDF receipts.
Merchant Configuration
Control what actions are available in the portal by passing a configuration object when creating the portal session.
| Option | Type | Default | Description |
|---|---|---|---|
| allowCancel | boolean | true | Allow customers to cancel their subscriptions |
| allowPause | boolean | false | Allow customers to pause their subscriptions |
| allowPaymentMethodUpdate | boolean | true | Reserved: card updates are not available yet |
| branding.primaryColor | string | #4F46E5 | Primary color for buttons and highlights (hex) |
| branding.logoUrl | string | null | URL to your company logo (displayed in portal header) |
| branding.companyName | string | null | Your company name (displayed in portal header) |
Integration Examples
Web (redirect)
function AccountPage() {
const handleManageSubscription = async () => {
const res = await fetch('/api/create-portal-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ customerId: currentUser.customerId }),
});
const { url } = await res.json();
window.location.href = url;
};
return (
<button onClick={handleManageSubscription}>
Manage Subscription
</button>
);
}Mobile (in-app browser)
import QuickPay
func openPortal() async {
let session = try await api.createPortalSession(customerId: currentUser.customerId)
QuickPay.presentPortal(url: session.url) { action in
switch action {
case .dismissed:
refreshAccountData()
default:
break
}
}
}Tip
After the customer returns from the portal, refresh your local subscription state by calling the Subscriptions API or relying on webhook events (e.g.subscription.cancelled,subscription.updated) to stay in sync.