← 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
cws_idstringYesApplication identifier
ref_idstringYesLogged-in customer's Reference ID
timestampintegerYesCurrent unix timestamp (seconds)

Body must be serialised with keys in this exact order: cws_id, then ref_id, then timestamp.

Signature

Compute HMAC-SHA512 of the raw request body bytes using your PORTAL_SECRET. Hex-encode the result and send it in the X-Portal-Signature header.

Serialise the body once, sign that string, then send that exact string — do not re-serialise or allow an HTTP library to reformat it. Key order affects the signature.

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

const timestamp = Math.floor(Date.now() / 1000);
const body = `cws_id=${CWS_ID}&ref_id=${uid}×tamp=${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,
});
# Python
import hmac, hashlib, time, requests

timestamp = int(time.time())
body = f"cws_id={CWS_ID}&ref_id={uid}×tamp={timestamp}"
sig  = hmac.new(PORTAL_SECRET.encode(), body.encode(), hashlib.sha512).hexdigest()

resp = requests.post(
	'https://pay.with.pink/api/portal/token/',
	data=body,  # pass raw string — do not use a dict (re-serialisation breaks the signature)
	headers={
		'Content-Type': 'application/x-www-form-urlencoded',
		'X-Portal-Signature': sig,
	},
)

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
400cws_id requiredcws_id 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
404Account was not found.ref_id has no PWP account
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.