Billing Portal
Generate a hosted billing portal URL for a logged-in customer.
Endpoint
POST /api/portal/token/
Headers
| Header | Value |
|---|---|
X-Portal-Signature | HMAC-SHA512 hex digest of the raw request body |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
application | string | Yes | Application identifier (hashid) shown in the PWP admin |
ref_id | string | Yes | Logged-in customer's Reference ID |
timestamp | integer | Yes | Current 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}×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,
});
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/"
}
| Field | Type | Description |
|---|---|---|
url | string | Redirect the customer to this URL |
Errors
Always check the HTTP status code before parsing the response body. On error the body is {"error": "<message>"}.
| Status | Error message | Cause |
|---|---|---|
| 400 | Application selector required | application missing from body |
| 400 | ref_id required | ref_id missing from body |
| 401 | Invalid signature | X-Portal-Signature does not match — check key order and raw-body HMAC |
| 401 | Request expired | timestamp missing, zero, or outside ±5 min window |
| 400 | Invalid Account provided. | ref_id has no PWP account for this application |
| 500 | Portal not configured | PWP-side configuration issue — contact PWP |
Token Lifetime
| Property | Value |
|---|---|
| Valid for | 1 hour from generation |
| After expiry | Portal page shows: _"This link has expired. Return to Autoview to access your billing portal."_ |
| Re-issue | Call 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.