Authentication
HashPay has two kinds of key and one capability rule. The split mirrors Stripe: a publishable key is safe in the browser, a secret key never leaves your server.
API keys
| Key | Prefix | Where it lives |
|---|---|---|
| Publishable | pk_test_… · pk_live_… | In your front-end. Safe to expose. |
| Secret | sk_test_… · sk_live_… | Server-side only. Never ship it to a browser. |
Keys are formatted <kind>_<mode>_<random>, so the prefix tells you both the kind and the mode at a glance. The mode is binding: a pk_test_ key only ever creates test data; a pk_live_ key only live.
Using a publishable key
Publishable-key endpoints (checkout) take the key in the JSON body as publishableKey, not in a header. The embed sets it for you from the script's data-pk.
{ "publishableKey": "pk_live_8sd7Fk…", "priceId": "price_123" }
Origin policy
The publishable key alone resolves your store; the request's Origin is a per-store policy filter on top:
- Live keys are blacklist-only: the only origin that's refused is one you've explicitly
rejected. Unknown,pendingandapprovedorigins all transact — an unknown origin is surfaced on your store for review after it produces a confirmed payment. A validOriginheader is still required. - Test keys are lenient — any origin works, localhost included, so you can integrate before reviewing anything.
A copied pk_live_ can only ever create payments that settle to your wallets — and the moment a foreign domain shows up in your review queue, rejecting it shuts checkout off there. Manage domains on the Domains page.
Using a secret key
Secret-key endpoints — Checkout Sessions, plus the whole store & account management surface under /v1/stores/… — authenticate with a bearer header from your backend:
Authorization: Bearer sk_live_2kfPqRsTuVwXyZ…
Capability access
Buyer-facing reads on a single payment — GET /payments/{id} and POST /payments/{id}/cancel — need no key at all. The unguessable pay_… id is the capability, which is what lets the widget poll status cross-origin without exposing anything sensitive. These responses contain only non-sensitive display fields.
What disables a live key
Live keys work by default. They go inert when any of these is true:
| Condition | Result |
|---|---|
Account suspended or banned | 403 account_disabled |
| Email not yet verified (email/password sign-ups) | 403 email_unverified |
Origin's domain is rejected (blacklisted), or the Origin header is missing | 403 origin_not_allowed / 400 origin_required |
| Account's monthly volume ceiling reached (checkout create only) | 402 volume_cap_reached |
Test keys are unaffected by the volume ceiling and email verification, and accept any origin — so your integration keeps working while you sort the above out. Full list on the Errors page.
Managing keys
Create, reveal, rotate and revoke keys on the Integration page of the dashboard.
- Publishable keys are shown in full any time — they're not secret.
- Secret keys are shown once at creation and then stored only as a hash; the dashboard displays a masked form afterward. Lost it? Mint a new one and revoke the old.
- Rotating is just minting a new key and revoking the previous one; revocation takes effect immediately.