API keys
Developers → API keys is where your integration’s credentials come from.
The portal is the only place a key’s secret is ever shown. No API endpoint returns it and neither do we.
Creating one
Section titled “Creating one”Create a key in the environment you intend to use it in — the switch at the top of the portal. You get:
psg_test_7hQ2mKpR9fL3xW8v_4bNq… sandboxpsg_live_7hQ2mKpR9fL3xW8v_4bNq… real moneyThe environment is in the key string itself, so you can tell which one you are
holding before you paste it into a config file. A test key against the live host —
or the reverse — is refused with environment_mismatch, which exists as its own
error because it is the most common integration mistake there is.
Choosing scopes
Section titled “Choosing scopes”A key carries an explicit list of what it may do, and you set it at creation.
Grant the narrowest set that works. A key that can only create and read
collections cannot be used to drain your balance if it leaks — and keys do leak, in
a committed .env, a shared screenshot, a log line.
The common shapes:
| The integration does | Scopes |
|---|---|
| Takes payments and watches them | collection.create, collection.read, transaction.read |
| Also issues refunds | add refund.create, refund.read |
| Pays people out | add payout.create, payout.read |
| Reads its own money | add balance.read, settlement.read |
| Manages webhook endpoints | add webhook.read, webhook.write |
Matching is exact — there are no wildcards, and transaction.read implies nothing
else. The full list is in Authentication.
A request with the right key and the wrong scope returns insufficient_scope, not
unauthenticated, so your developer can tell the two apart without guessing.
Lock it to your servers
Section titled “Lock it to your servers”If your integration calls from fixed addresses, add them as an IP allowlist on the
key. A leaked key is then unusable from anywhere else, which turns a serious
incident into a nuisance. Requests from elsewhere are refused with
ip_not_allowed.
This is the single highest-value thing on this page and it takes a minute.
Rotating without an outage
Section titled “Rotating without an outage”Rotation does not kill the old key immediately — there is a grace period, so you can redeploy at your own pace.
- Rotate in the portal. You get a new secret, once.
- Deploy it everywhere that holds the old one.
- Check that nothing is still using the old key. Every response still
authenticated by the superseded key carries the header
PaySigna-Key-Grace-Expires, with the moment it stops working.
Step 3 is the one people skip, and it is the one that matters. Without it, a rotation’s grace period is invisible to the only party who can act on it: the requests all succeed, you have no way to tell which of your servers is still presenting the old credential, and you find out when every one of them fails at the same instant. Tell your developer to alert on that header.
After the grace period the old key returns api_key_revoked — distinct from
unauthenticated, so the message is “rotate”, not “check your spelling”.
One key per system
Section titled “One key per system”Not one key shared across everything. Separate keys for your website, your back-office job and your mobile backend mean a rotation is a contained operation instead of a coordinated outage, and a leak tells you where it came from.
Rotate immediately if
Section titled “Rotate immediately if”- A key has been in a repository, a chat message, a screenshot or a support ticket.
- Someone with access to it has left.
- You see requests you cannot account for.
- You are unsure. Rotation is cheap and a compromised payment key is not.
For anything that looks like a real compromise, also write to [email protected].
Never put a secret key in a browser or an app
Section titled “Never put a secret key in a browser or an app”A secret key in frontend JavaScript or a mobile binary is public, whatever it is
wrapped in. For things a customer’s browser legitimately needs to do, use a
publishable key (psg_pk_…), which is restricted at the database level to
client-safe scopes, or a client token, which is minted for one transaction and
expires within the hour.
That distinction is also why the request console in our API reference only ever talks to the sandbox.

