> ## Documentation Index
> Fetch the complete documentation index at: https://docs.macropay.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Customer Portal

> Give customers a self-serve hub for invoices, subscriptions, and benefits — no support tickets required

Every "where's my invoice?" or "how do I cancel?" email is a support cost you don't need to pay. The Customer Portal hands those jobs to your customers: a hosted, branded surface where they manage their own subscriptions, pull their own receipts, and grab the benefits they paid for.

Because Macropay is the merchant of record, the portal also doubles as the customer's tax-compliant billing record. Every invoice it serves already reflects the correct sales tax or VAT we collected and remit on your behalf — so the document your customer downloads is the one their accountant accepts, with zero tax plumbing on your side.

## What customers can do

<CardGroup cols={2}>
  <Card title="Invoices & receipts" icon="file-invoice">
    View and download tax-correct invoices for every order
  </Card>

  <Card title="Subscriptions" icon="arrows-rotate">
    Upgrade, downgrade, switch plans, or cancel
  </Card>

  <Card title="Benefits" icon="gift">
    Retrieve license keys, file downloads, and other entitlements
  </Card>

  <Card title="Usage charges" icon="gauge">
    Inspect metered and usage-based line items before they bill
  </Card>
</CardGroup>

You can hand customers a fully hosted portal (no code), generate authenticated session links from your backend, or embed the whole thing inside your app.

## Before you start

* A [Macropay account](https://macropay.ai/signup) with an organization
* An [organization access token](/integrate/authentication) for API calls
* At least one customer who has placed an order or holds a subscription

<Info>
  Work through this guide in the [Sandbox](https://sandbox.macropay.ai/start) first. You can spin up test customers and subscriptions without moving real money.
</Info>

## The fastest path: hosted portal

If you don't want to write any code yet, you don't have to. Macropay hosts a portal at a public URL for your organization:

```
https://macropay.ai/your-org-slug/portal
```

A customer enters the email they used at checkout, and we send a magic link to verify them — no password to manage, no auth code for you to write. Drop this link in your footer, your purchase-confirmation email, or your help center and you're done.

When you're ready for a tighter, logged-in experience, generate session links instead.

## Authenticated access with session links

To send a signed-in customer straight into their portal, mint a customer session on your backend and redirect them to the returned URL. The token is short-lived and scoped to a single customer, so it's safe to generate on demand.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { Macropay } from "@macropayments/sdk";

  const macropay = new Macropay({
    accessToken: process.env.MACROPAY_ACCESS_TOKEN!,
    server: "sandbox",
  });

  const session = await macropay.customerSessions.create({
    customerId: "cust_8Qa2v",
  });

  // Send the customer here — they're already authenticated
  console.log("Portal URL:", session.customerPortalUrl);
  ```

  ```py Python theme={null}
  from macropay import Macropay
  import os

  client = Macropay(
      access_token=os.environ["MACROPAY_ACCESS_TOKEN"],
      server="sandbox",
  )

  session = client.customer_sessions.create(
      customer_id="cust_8Qa2v",
  )

  # Send the customer here — they're already authenticated
  print(f"Portal URL: {session.customer_portal_url}")
  ```

  ```bash curl theme={null}
  curl -X POST https://sandbox-api.macropay.ai/v1/customer-portal/sessions \
    -H "Authorization: Bearer $MACROPAY_ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "customer_id": "cust_8Qa2v"
    }'
  ```
</CodeGroup>

<Tip>
  Resolve the Macropay `customer_id` from your own user record — never expose it in client-side code or URLs. The session token in `customerPortalUrl` is what grants access, and it expires on its own.
</Tip>

### Redirect from a route

A "Manage billing" button is just a backend route that creates a session and 302s the customer over. Here it is as a Next.js route handler:

```ts theme={null}
// src/app/portal/route.ts (Next.js)
import { redirect } from "next/navigation";
import { Macropay } from "@macropayments/sdk";

const macropay = new Macropay({
  accessToken: process.env.MACROPAY_ACCESS_TOKEN!,
  server: "sandbox",
});

export async function GET(req: Request) {
  // Map your logged-in user to their Macropay customer ID
  const customerId = await getCustomerIdFromSession(req);

  const session = await macropay.customerSessions.create({
    customerId,
  });

  redirect(session.customerPortalUrl);
}
```

Then link to it from anywhere in your UI:

```tsx theme={null}
<a href="/portal">Manage billing</a>
```

## Embedding the portal in your app

Prefer to keep customers inside your own product? Render the portal in an iframe. A small client component fetches the session URL from your API and drops it in:

```tsx theme={null}
// src/app/account/page.tsx
"use client";

import { useEffect, useState } from "react";

export default function AccountPage() {
  const [portalUrl, setPortalUrl] = useState<string | null>(null);

  useEffect(() => {
    fetch("/api/portal-session")
      .then((res) => res.json())
      .then((data) => setPortalUrl(data.url));
  }, []);

  if (!portalUrl) return <div>Loading your billing details…</div>;

  return (
    <iframe
      src={portalUrl}
      style={{ width: "100%", height: "800px", border: "none" }}
      title="Billing"
    />
  );
}
```

Back it with an API route that creates the session server-side, keeping your access token off the client:

```ts theme={null}
// src/app/api/portal-session/route.ts
import { Macropay } from "@macropayments/sdk";

const macropay = new Macropay({
  accessToken: process.env.MACROPAY_ACCESS_TOKEN!,
  server: "sandbox",
});

export async function GET(req: Request) {
  const customerId = await getCustomerIdFromSession(req);

  const session = await macropay.customerSessions.create({
    customerId,
  });

  return Response.json({ url: session.customerPortalUrl });
}
```

## Match it to your brand

The portal should feel like your product, not ours. Configure its look in the [dashboard](https://app.macropay.ai):

<Steps>
  <Step title="Open branding settings">
    Go to **Settings** → **Branding**.
  </Step>

  <Step title="Upload your logo">
    Add a logo for the portal header and emails.
  </Step>

  <Step title="Set your colors">
    Choose a primary brand color and accent colors.
  </Step>
</Steps>

Every hosted page and email then inherits those styles, so the experience stays consistent whether a customer arrives via magic link, session redirect, or embed.

## A note on what customers never see

The portal exposes billing history and benefits — never raw card data. Payment details are tokenized in our PCI DSS Level 1 compliant vault, so the portal renders only a safe reference (brand and last four) and card entry happens on our hardened surface. Combined with merchant-of-record coverage, that keeps both PCI scope and tax liability off your plate.

## Where to go next

<CardGroup cols={2}>
  <Card title="Customer management" icon="users" href="/features/customer-management">
    How customer records and states work
  </Card>

  <Card title="Customer state API" icon="database" href="/integrate/customer-state">
    Read a customer's entitlements straight from your backend
  </Card>

  <Card title="Benefits" icon="gift" href="/features/benefits/introduction">
    Set up license keys, downloads, and other perks
  </Card>

  <Card title="Subscription changes" icon="arrows-rotate" href="/guides/subscription-upgrades">
    Drive upgrades and downgrades from your own UI
  </Card>
</CardGroup>
