Skip to Content
PaymentsAPI ReferenceSigned Requests

Signed Requests

Some API requests require a cryptographic signature to authorize the action. This applies to requests that move funds — specifically, capturing and refunding payments in two-step mode.

Why Are Some Requests Signed?

Your API key authenticates who you are, but it is not enough to move funds. Captures and refunds transfer stablecoins between wallets, so they require proof that you — the merchant — explicitly authorized the action.

This is done with a signing key: a separate cryptographic key-pair that you control. You sign the request body with your private key and include the signature in the X-Signature header. The API verifies the signature before executing the action.

🔐

Your API key alone cannot move funds. Captures and refunds require a valid signature from your signing key.

Setting Up Your Signing Key

1. Generate a Key-Pair

Generate an ECDSA key-pair using any of the following tools:

openssl ecparam -name secp256k1 -genkey -noout -out signing-key.pem && openssl ec -in signing-key.pem -pubout -out signing-key.pub.pem

This creates signing-key.pem (private — keep secret) and signing-key.pub.pem (public — send to Exodus).

2. Register Your Public Key

Send your public key to your account manager. Once received, we will configure your account for two-step payments.

Signing a Request

When making a capture or refund request, sign the JSON-serialized request body using ECDSA with your private key and include the signature in the X-Signature header.

Request Body

The request body is a JSON object containing the action and payment ID:

Capture:

{
  "action": "capture",
  "payment_id": "pay_0987654321fedcba"
}

Refund:

{
  "action": "refund",
  "payment_id": "pay_0987654321fedcba"
}

Creating the Signature

import crypto from 'crypto';
 
function signRequest(body, privateKey) {
  const payload = JSON.stringify(body);
  const sign = crypto.createSign('SHA256');
  sign.update(payload);
  return sign.sign(privateKey, 'hex');
}
 
// Example: sign a capture request
const body = {
  action: 'capture',
  payment_id: 'pay_0987654321fedcba',
};
 
const signature = signRequest(body, process.env.SIGNING_PRIVATE_KEY);
 
// Send to the API
const response = await fetch('https://checkout.exodus.com/payments/pay_0987654321fedcba/capture', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer sk_live_xxxxxxxxxxxxxxxx',
    'Content-Type': 'application/json',
    'X-Signature': signature,
  },
  body: JSON.stringify(body),
});

Security Best Practices

  1. Keep your private key secret — store it in a secure environment (e.g., environment variables, secrets manager). Never expose it in client-side code or version control.
  2. Separate from API key — your signing key and API key serve different purposes. Compromising one does not compromise the other.
  3. Rotate if compromised — if you suspect your signing private key has been compromised, contact your account manager immediately to register a new public key.

Start building

XO

Request Demo

Schedule a call with our team

Select a product
Arrow right

Start building
Grateful

Contact Us

We're here to help