← Back to docs

Billing Portal

Generate a hosted billing portal URL for a logged-in customer.

Endpoint

POST /api/portal/token/

Headers

HeaderValue
X-Portal-SignatureHMAC-SHA512 hex digest of the raw request body

Request Body

FieldTypeRequiredDescription
applicationstringYesApplication identifier (hashid) shown in the PWP admin
ref_idstringYesLogged-in customer's Reference ID
timestampintegerYesCurrent unix timestamp (seconds)

Include application in every request.

Signature

Code

// Bun
import { createHmac } from 'crypto';

const timestamp = Math.floor(Date.now() / 1000);
const body = `application=${APPLICATION_REF}&ref_id=${uid}&timestamp=${timestamp}`;
const sig  = createHmac('sha512', PORTAL_SECRET).update(body).digest('hex');

await fetch('https://pay.with.pink/api/portal/token/', {
	method: 'POST',
	headers: {
		'Content-Type': 'application/x-www-form-urlencoded',
		'X-Portal-Signature': sig,
	},
	body,
});

Details

Compute HMAC-SHA512 with the application's PORTAL_SECRET and send the hex-encoded digest in X-Portal-Signature. PWP signs the exact raw body bytes; serialize the body once, sign that string, and send it without re-encoding. The request must include a unix-seconds timestamp within five minutes of server time and use application/x-www-form-urlencoded.

Response

{
  "url": "https://pay.with.pink/portal/Mj3k9x/"
}
FieldTypeDescription
urlstringRedirect the customer to this URL

Errors

Always check the HTTP status code before parsing the response body. On error the body is {"error": "<message>"}.

StatusError messageCause
400Application selector requiredapplication missing from body
400ref_id requiredref_id missing from body
401Invalid signatureX-Portal-Signature does not match — check key order and raw-body HMAC
401Request expiredtimestamp missing, zero, or outside ±5 min window
400Invalid Account provided.ref_id has no PWP account for this application
500Portal not configuredPWP-side configuration issue — contact PWP

Token Lifetime

PropertyValue
Valid for1 hour from generation
After expiryPortal page shows: _"This link has expired. Return to Autoview to access your billing portal."_
Re-issueCall this endpoint again — a new token is created with a fresh 1-hour window

Generate the token on demand (when the user clicks the billing button) rather than pre-generating.