Skip to Content
CheckoutAPI ReferenceSigned Requests

Signed Requests

Some API requests require a cryptographic signature to authorize the action. This applies to any request that creates or moves funds. That includes creating direct payments, capturing, and refunding payments, as well as all merchant-driven subscription actions (charge, cancel).

Why Are Some Requests Signed?

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

This authorization is done with a signing key: a separate cryptographic key-pair that you control. The SDK provides purpose-built signing functions for each action; you call the appropriate function with your signing key and include the resulting signature with the request. The API verifies the signature before executing the action.

🔐

Your API key alone cannot move funds. All fund-moving operations require a valid signature from your signing key.

Setting Up Your Signing Key

Install the SDK — you use it to sign every fund-moving request:

npm install @exodus/checkout-signer
📦

These examples use the CheckoutSigner client from @exodus/checkout-signer 0.5.0 or later, which takes the single sgk_… Signing Key. Earlier releases took a raw mnemonic.

Get Your Signing Key

Generate a Signing Key in the Exodus dashboard. Under Settings → Signing key, generate a single sgk_… secret, shown once, that you treat like an API key. The dashboard registers your signer addresses in the same step, so your account is ready to sign with nothing to send separately, and it handles rotation. Store the key as SIGNING_KEY.

Signing a Request

You sign every fund-moving request with the CheckoutSigner client (below). Where the signature goes depends on the request:

  • Checkout creation (direct payments): the signature travels in the request body, inside the signatures array. A checkout can be payable on more than one chain, so it carries one entry per chain family. Each entry is { chain_family, on_chain_id, signature, deadline }. These per-chain deploy signatures are data stored on the payment, so they belong in the body.
  • Every other signed action (charge, cancel, capture, refund, rescue, settlement change): a single signature authorizes one operation, so it travels in the X-Signature header (the action parameters go in the body).

In short: the signatures that describe the resource go in the body; the one signature that authorizes a request goes in the header.

On EVM chains, every merchant signature (payments and subscriptions) is EIP-712 typed data, so it renders as structured, human-readable fields in the signer’s wallet rather than as an opaque hash. Solana actions are signed as ed25519 messages.

Construct CheckoutSigner once (with no arguments it reads your SIGNING_KEY from the environment), then call the method for your action with the API response object. It derives the right per-chain key, fills in the signed fields, defaults the deadline, and returns { signature, body }.

import { CheckoutSigner } from '@exodus/checkout-signer';
 
// Reads SIGNING_KEY from the environment
const signer = new CheckoutSigner();

Subscriptions

Pass the GET /subscriptions/:id object straight in — subscriptions sign on any supported chain (the client reads the chain off the object).

// Charge — you choose the amount per charge (the plan price here); the body carries the same amount
const { signature } = signer.signCharge(subscription, { amount: BigInt(subscription.price) });
// Send signature in the X-Signature header and { amount: subscription.price } as the body
// to POST /subscriptions/:id/charge
 
// Cancel (immediate, on-chain, terminal) — body carries the deadline the signature commits to
const cancel = signer.signCancelSubscription(subscription);
// Send cancel.signature in X-Signature and cancel.body to POST /subscriptions/:id/cancel

Two-step payments

Pass the GET /payments/:id object (or the payment.escrow_confirmed webhook payload).

// Capture — amount comes from the payment's token_amount
const capture = signer.signCapture(payment);
// Send capture.signature in X-Signature and capture.body to POST /payments/:id/capture
 
// Refund to the customer — destination is bound into the signature
const refund = signer.signRefund(payment, { destination }); // e.g. destination = payer_addresses[0]
// Send refund.signature in X-Signature and refund.body to POST /payments/:id/refund

Direct payment (checkout creation)

const signatures = signer.createDirectPayment({ factory_addresses: { evm: factoryAddress } });
// signatures = { evm: { on_chain_id, signature, deadline }, ... }
// Send one entry per family as { chain_family, on_chain_id, signature, deadline } in the `signatures` array of POST /checkouts

Rescue and settlement change

// Rescue stuck funds — pass the payment object plus the token to recover and its receiver
const rescue = signer.signRescue(payment, { token, receiver });
// Send rescue.signature in X-Signature and rescue.body to POST /payments/:id/rescue
 
// Change the settlement address — read contract_address + nonce from GET /payments/settlement
const settlement = signer.signSettlementChange({ contract_address, new_address, nonce, chain });
// Send settlement.signature in X-Signature and settlement.body to PUT /payments/settlement

Raising the cap is subscriber-authorized, not merchant-signed: the subscriber calls updateCap on-chain. A merchant key can only charge within the subscriber’s existing cap and budget, or cancel.

Security Best Practices

  1. Keep your private key secret. Store it in a secure environment such as environment variables or a secrets manager. Never expose it in client-side code or version control.
  2. Separate it from your API key. Your signing key and API key serve different purposes, so compromising one does not compromise the other.
  3. Rotate it if compromised. If you suspect your signing private key has been compromised, contact your account manager immediately to register a new signer address.

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