Skip to main content
POST
/
api
/
payments
/
billing-portal
Billing portal
curl --request POST \
  --url https://api.example.com/api/payments/billing-portal \
  --header 'Authorization: <authorization>'
{
  "message": "<string>",
  "data": {
    "url": "<string>"
  }
}
This endpoint requires a valid Bearer token and an existing Stripe customer account. Users on the free plan who have never subscribed will receive a 400 error.
Creates a Stripe Customer Portal session and returns a redirect URL. The portal is a Stripe-hosted page where users can:
  • Update or replace their payment method
  • View past invoices and download receipts
  • Cancel or reactivate a scheduled cancellation
  • Review billing history
After the user is done, Stripe redirects them back to the /settings page in the Hayon frontend.

Authentication

Authorization
string
required
Bearer token. Format: Bearer <token>.

Prerequisites

The authenticated user must have a stripeCustomerId on file. This is set automatically when the user first completes a checkout session. Calling this endpoint without a prior subscription returns:
{
  "message": "No billing account found. Please subscribe first."
}

Response

On success the server returns 200 OK with a JSON body containing a single-use Stripe Customer Portal URL.
message
string
Human-readable status message.
data
object

Behavior

  • Changes made in the portal (e.g., toggling a cancellation) are communicated back to the server via Stripe webhooks. The customer.subscription.updated event keeps the database in sync.
  • The portal session return URL is configured to send the user back to /settings after they exit.
The returned url is single-use. If the user’s session expires, call this endpoint again to generate a fresh URL.

Example

curl --request POST \
  --url https://api.hayon.app/api/payments/billing-portal \
  --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'

Success response

{
  "message": "Billing portal session created",
  "data": {
    "url": "https://billing.stripe.com/p/session/test_YWNjdF8xTzBTVGFCdUhyZ3JhTkJ4LF9RZlQ0eUhwUkdMRzFmRFR5d2lQUFFZVG9jbloxSnlm0100KgiqMBKb"
  }
}

Error responses

400
{
  "message": "No billing account found. Please subscribe first."
}
401
{
  "message": "Unauthorized"
}
500
{
  "message": "Failed to open billing portal"
}

Portal flow

1

Request a portal URL

Call POST /api/payments/billing-portal with the user’s Bearer token.
2

Redirect the user

Redirect the browser to the url returned in the response body.
3

User manages their subscription

The Stripe-hosted portal lets the user update payment methods, view invoices, and manage cancellation.
4

User returns to settings

After exiting the portal, Stripe redirects the user to /settings. Any subscription changes are synced via webhook.

Build docs developers (and LLMs) love