Skip to content

Customer portal

The customer portal is a Paylera-hosted web experience for end users to manage their own subscription, payment methods, and invoices. You generate a session URL; we host the rest. No additional UI work.

What it does

  • Subscription — view current plan, upgrade / downgrade, cancel, reactivate.
  • Payment methods — add, remove, set default.
  • Invoices — list, download PDFs, retry failed payments.
  • Tax IDs — add or remove tax registration numbers.
  • Billing address — update billing address.

What it does not do:

  • Show usage or current period totals (build that in your app — use GET /v1/subscriptions/{id}/usage).
  • Sign the customer in. You authenticate them; we trust your handoff.

Configuration

In Settings → Customer portal, enable the modules you want exposed (you can hide cancellation, hide downgrades, hide tax IDs, etc.). All edits go through validated flows; no manual intervention needed.

Generating a session

POST /v1/portal/sessions
Idempotency-Key: <uuid>
{
"customer_id": "cus_…",
"return_url": "https://yourapp.example/account/billing"
}

Response:

{
"id": "ps_…",
"url": "https://portal.paylera.io/p/ps_…",
"expires_at": "2026-05-06T13:34:00Z"
}

Redirect the customer to url. The link is single-use and expires in 60 minutes.

When the customer is done (closes the tab, hits back to app), they land on return_url.

Locking access

The portal session is tied to a single customer_id. The customer cannot navigate to other customers’ data. The session URL is a bearer-style token — anyone with it can act on that customer until the TTL expires. Keep it server-side; don’t email it raw.

For long-lived deep links from your app’s UI, generate the URL on demand at click-time, not at page-render time.

Locale and branding

The portal inherits your tenant’s branding (logo, accent colour). Locale is auto-detected from Accept-Language; override with locale: "fr" on session create.

Restricting actions

Per-session overrides for one-off flows:

{
"customer_id": "cus_…",
"return_url": "https://yourapp.example/account/billing",
"configuration": {
"subscription_cancel_enabled": false,
"subscription_update_enabled": false,
"payment_method_update_enabled": true
}
}

Useful for “update card” emails after a payment failed — you don’t want the customer accidentally cancelling instead of updating.

What you’ll get back

Every action the customer takes emits a webhook from your tenant’s configured endpoints. Listen for:

EventWhen
subscription.updatedPlan changed, quantity changed.
subscription.canceledCustomer cancelled.
subscription.reactivatedCustomer un-cancelled before period end.
customer.payment_method_attachedCustomer added a method.
customer.payment_method_detachedCustomer removed a method.
customer.updatedAddress or tax ID changed.
portal.session.completedCustomer closed the portal.

portal.session.completed carries a summary of actions taken — use it to refresh local caches in one shot.

Embedding

The portal isn’t designed to be embedded in an iframe (we ship X-Frame headers). If you need fully embedded UX, build the surface in your app using the REST endpoints — they’re the same ones the portal uses.

Common pitfalls

  • Session leaked into a URL log: portal URLs are bearer credentials for the duration of the TTL. Don’t include them in 302 redirects visible in client-side logs.
  • Generating sessions in advance: TTL is 60 minutes. Generate at click time, not signup time.
  • Customer expects to update something the portal doesn’t expose: expand the configuration in Settings → Customer portal.