Skip to content

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.

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… sandbox
psg_live_7hQ2mKpR9fL3xW8v_4bNq… real money

The 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.

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.

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.

Rotation does not kill the old key immediately — there is a grace period, so you can redeploy at your own pace.

  1. Rotate in the portal. You get a new secret, once.
  2. Deploy it everywhere that holds the old one.
  3. 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”.

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.

  • 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.