BillStack Docs

6. Customer Portal

Replace Stripe's Customer Portal with BillStack's self-hosted portal

What the Portal Provides

BillStack includes a self-hosted customer portal at /portal/[token] that provides:

  • Subscription management — view active subscriptions, cancel at period end, resume canceled subscriptions
  • Invoice history — view and download PDF invoices
  • Payment methods — add or update payment methods via Stripe's secure Setup mode

Unlike Stripe's Customer Portal, the BillStack portal is fully self-hosted, customizable, and project-scoped.

How Portal Access Works

Portal access is token-based with no customer login required:

  1. Your SaaS app calls the BillStack portal API to generate a session token
  2. BillStack returns a one-time URL with the token embedded
  3. You redirect your customer to that URL
  4. The token is validated server-side — it includes the customer ID and expiration
  5. The portal renders the customer's data directly

Generate a Portal Session

Before — Stripe Customer Portal:

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

const session = await stripe.billingPortal.sessions.create({
  customer: 'cus_xxx',
  return_url: 'https://myapp.com/account',
});
// Redirect to session.url

After — BillStack Portal:

const response = await fetch(
  `${BILLSTACK_URL}/api/billstack/teams/${TEAM_ID}/projects/${PROJECT_ID}/portal`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      customerId: 'pc_xxx',    // BillStack customer ID
      ttlMs: 3600000,          // optional: 1 hour (default)
    }),
  }
);

const { sessionId, token, portalUrl, expiresAt } = await response.json();
// Redirect to portalUrl

Response:

{
  "sessionId": "portal_sess_abc123",
  "token": "a1b2c3d4e5f6...",
  "portalUrl": "https://your-billstack.com/portal/a1b2c3d4e5f6...",
  "expiresAt": "2026-03-30T13:00:00Z"
}

Portal Features

Subscription Management

Customers can view their active subscriptions and:

  • Cancel at period end — subscription remains active until the billing period ends
  • Resume — reactivate a subscription that was set to cancel at period end

These actions call PATCH /api/portal/[token]/subscriptions/[subscriptionId] with { cancelAtPeriodEnd: true/false }.

Invoice History

Customers see a list of their past invoices with:

  • Invoice number and date
  • Amount and status (paid, open, void)
  • Download PDF links (direct from Stripe)

Payment Methods

Customers can update their payment method:

  1. Click Update Payment Method
  2. BillStack calls POST /api/portal/[token]/setup-intent to create a Stripe Setup Intent
  3. The customer is redirected to Stripe's secure checkout in setup mode
  4. After completing, the new payment method is attached to their Stripe customer

Integration Pattern

A typical integration in your SaaS app:

// In your SaaS app's account/billing page
export default function BillingPage() {
  async function openPortal() {
    const res = await fetch('/api/billing/portal', { method: 'POST' });
    const { portalUrl } = await res.json();
    window.location.href = portalUrl;
  }

  return (
    <button onClick={openPortal}>
      Manage Billing
    </button>
  );
}
// /api/billing/portal route in your SaaS app
export async function POST(req: Request) {
  const user = await getAuthenticatedUser(req);

  const res = await fetch(
    `${BILLSTACK_URL}/api/billstack/teams/${TEAM_ID}/projects/${PROJECT_ID}/portal`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        customerId: user.billstackCustomerId,
      }),
    }
  );

  return Response.json(await res.json());
}

Security

  • Portal tokens are short-lived (default: 1 hour, configurable via ttlMs)
  • Tokens are validated server-side on every request
  • Customers can only see and manage their own data
  • Payment method updates go through Stripe's secure hosted page — card details never touch BillStack

Next Step

The core migration is complete. Optionally, set up the referral system to grow your customer base.

On this page