Skip to content

API keys, permissions and domains

You create and manage API keys in the dashboard under Developers → API keys.

Type Prefix Where it is used
Secret key ctp_test_sk_, ctp_live_sk_ On your server only. It can do whatever its permissions allow, which can include sending payouts.
Publishable key ctp_test_pk_, ctp_live_pk_ Safe to put in a web page. It can call only three endpoints, which return reference data: list networks, list assets and get a rate.

Every other endpoint refuses a publishable key with status 403 and the code publishable_key_not_allowed.

The middle of the prefix says which mode the key belongs to. See Test mode and live mode.

A key is shown once, when you create it. cTpeWay stores only a hash of it and cannot show it again. Afterwards the dashboard lists each key by its prefix and last four characters, with the date it was created and when it was last used.

Send your secret key as a bearer token in the Authorization header of every request.

Terminal window
curl "https://api.ctpeway.com/v1/webhook_endpoints" \
-H "Authorization: Bearer $CTPEWAY_API_KEY"

With the Node SDK you pass the key once:

import { CtpeWay } from '@ctpeway/sdk';
const ctpeway = new CtpeWay(process.env.CTPEWAY_API_KEY!);

A request with a missing, wrong or revoked key is answered with 401 and an error of type authentication. See Errors.

The merchant API is meant for servers. It does not allow calls from a web page, and a secret key must never be put in a browser or a mobile app. The SDK refuses to start in a browser for this reason.

Each secret key has a list of permissions. Give a key only what its job needs. A key that only creates invoices should not be able to send payouts.

Permission Allows
invoices:read, invoices:write Reading invoices; creating and canceling them.
deposit_addresses:read, deposit_addresses:write Reading reusable deposit addresses; creating and changing them.
payments:read Reading payments.
settlements:read, settlements:release Reading settlements; asking for a release attempt.
payouts:read, payouts:write Reading payouts; sending them.
refunds:write Creating refunds. A refund is paid as a payout, so the key needs payouts:write as well.
webhooks:manage Managing webhook endpoints.
master_wallet:read Reading master wallet addresses and balances.
test:simulate Using the test-mode simulators.

A request that needs a permission the key does not have is answered with 403, an error of type permission and the code insufficient_scope.

A new secret key cannot send payouts or create refunds. Two things have to be in place first:

  • The permission. payouts:write for payouts, and refunds:write as well for refunds. No key has them unless you grant them when you create the key.
  • A limit on where the key can send money. At least one of these, set under API keys in the dashboard:
    • An IP allowlist. The key works only from the IP addresses you list, and can then pay or refund to any address that is valid on the payout’s network. This suits a platform that pays its own customers, as a v1 integration did.
    • A payout allowlist. The key pays out, and refunds, only to the destination addresses you list, wherever the request comes from. This suits fixed destinations, such as your exchange account or your suppliers.

With both, a payout must come from one of your IP addresses and go to one of the listed destinations. Every other check on the destination still applies: it must be a valid address on the network, and not one of your own addresses.

A key with the permission and neither list cannot be created, and a change that would leave it with neither is refused (payout_allowlist_required), so a key cannot drop the IP allowlist it relies on. When a payout is refused the API answers with insufficient_scope, payout_allowlist_required, payout_destination_not_allowlisted or ip_not_allowed.

A key can never do more than the team member who creates it may do in the dashboard.

  • You can give a key only the permissions your own role holds.
  • You cannot change or roll a key that carries a permission your role does not hold.
  • A payout allowlist can be set only by a role that is allowed to pay out.

For example, a developer cannot grant payouts:write, refunds:write or settlements:release. An owner or an admin can.

Use a separate key for payouts and keep its lists short. See Paying out from your master wallet.

You can restrict a key to a list of IP addresses or ranges, IPv4 or IPv6, for example 203.0.113.24 or 2001:db8::/48. Requests from anywhere else are refused with ip_not_allowed. If your servers have fixed addresses, use it. A range ending in /0 lets every address in, so it does not count as a restriction for a key that pays out.

cTpeWay checks the address that connects to it. If your requests leave through a proxy or a NAT gateway of your own, list the address the requests come out from.

  • Roll a key to replace it. The old key keeps working for an overlap period, 24 hours by default and up to 7 days, so you can deploy the new key without downtime.
  • Revoke a key to stop it at once.

Roll a key at once if it may have leaked.

You verify a domain to prove that you control it. You do it in the dashboard under Developers → Domains:

  1. Add the domain.
  2. Choose a method and copy the value the dashboard gives you:
    • DNS record: add a TXT record at _ctpeway-verify.<your domain>.
    • File: serve a file at /.well-known/ctpeway-verify.txt on the domain.
  3. Choose Verify.

A domain is Pending, Verified or Failed, with the reason when it fails. cTpeWay checks verified domains again every week.

In live mode, verified domains are the only hostnames allowed for:

  • webhook endpoint URLs,
  • the success_url and cancel_url of an invoice.

In test mode, localhost and hostnames ending in .test are allowed as well.

What a domain binding does and does not protect

Section titled “What a domain binding does and does not protect”

It does not protect a key. A browser tells a server which site a request comes from, but only browsers do. Any other program can send whatever it likes. So nothing in the API relies on where a request says it comes from, for either kind of key. A publishable key is safe in a web page because of how little it can do, not because of where it is used.

For a secret key in particular, a domain is no protection. A secret key is used by a server, and a server can put anything it likes in a request. Tying a secret key to a domain would not stop someone who has stolen the key from using it somewhere else, so cTpeWay does not pretend that it does.

What verifying a domain does give you, with a secret key:

  • Webhooks are only sent to hostnames you have proved you control.
  • The checkout only redirects your customers to hostnames you have proved you control. Nobody can use a cTpeWay checkout page to send people to another site.

What actually limits a stolen secret key:

  • Permissions: the key can only do what you allowed.
  • The IP allowlist: the key only works from your servers. A key that pays out without a payout allowlist relies on it.
  • The payout allowlist: the key can pay only to the addresses you listed.
  • Rolling: replacing the key ends the old one.

An account can be put on hold or blocked. This is what your integration sees:

Account Effect The API answers
On hold Payouts and refunds cannot be created. merchant_on_hold
Blocked Nobody can sign in to the dashboard, and the account’s API keys do not work. merchant_suspended
  • Keep keys in environment variables or a secret manager. Never commit them to a repository.
  • Use a separate key for each system, so that you can roll one without touching the others.
  • Use test keys while developing. A live key belongs only in your production environment.
  • Never log a key. The SDK and the command-line tool do not print keys, including in error messages.

Keys created in cTpeWay v1 do not work in v2. Create a new key, and send it as a bearer token in the Authorization header instead of in x-api-key. See Moving from the v1 API.