Getting Started

This section covers the Checker Payments API.

Payments API Guides

Use the Payments API to onboard customers, open accounts, register beneficiaries, and move money across supported corridors. Every corridor follows the same sequence:

Create a customer → Onboard → Open an account → Fund it → Add a beneficiary → Send a payout

Two corridors are documented here:

Use caseAccountsFundingPayouts
USD AccountsUSD account plus stablecoin wallets, in the customer's own nameInbound bank credit (ACH, Fedwire, SWIFT)Bank payout on ACH, FEDWIRE, SWIFT; on-chain payout on CRYPTO
Payouts to ChinaStablecoin walletsOn-chain depositQuote-driven CHINA_OFFRAMP payout to a China bank account

Corridor availability depends on your entitlements. Call GET /v1/payments/providers for the corridor identifiers enabled for your integration; the examples below use provider-a, provider-b, and provider-c as placeholders.

Individual Payout Limits is documented alongside them. It is not a corridor but a follow-on flow: once an individual customer is onboarded, read their payout limits and request higher ones.


Core Concepts


Object Hierarchy

  • Customer — the legal entity, business or natural person, on whose behalf accounts and payments are managed.
  • Account — a payment account for a customer, one per currency or asset. Provisioned automatically when onboarding completes.
  • Beneficiary — a bank account or on-chain wallet that can receive payouts.
  • Deposit — an inbound credit to an account. Credited when it reaches COMPLETED.
  • Quote — a time-boxed exchange rate between two currencies. Backs conversions and quote-driven payouts.
  • Conversion — execution of a quote that settles into an account balance, independent of any later payout.
  • Withdrawal — an outbound payment from an account to a beneficiary.
  • Transaction — the unified view of deposits, withdrawals, and conversions.
  • Limit request — a request to raise an individual customer's payout limits, reviewed asynchronously.

Base URLs

Build against sandbox first, then switch to production.

EnvironmentBase URL
Sandboxhttps://api-staging.checker.finance
Productionhttps://api.checker.finance

All endpoints sit under the base path /v1/payments.


Authentication

Every request is signed with your API secret using HMAC-SHA256.

HeaderDescription
X-Api-KeyYour API key.
X-TimestampCurrent UTC time, RFC 3339 (e.g. 2026-10-05T14:03:00Z). Must be within 5 minutes of server time.
X-NonceA fresh random value per request (e.g. a UUID v4). Not signed.
X-SignatureLowercase hex HMAC-SHA256 of the string to sign, keyed with your API secret.

String to sign: five lines joined with \n, no trailing newline.

METHOD
PATH
QUERY
TIMESTAMP
BODY
  • METHOD: upper case.
  • PATH: path only, e.g. /v1/payments/accounts.
  • QUERY: query string without ?, empty if none. Encode it exactly as sent: spaces as +, other reserved characters as upper-case %XX.
  • TIMESTAMP: the exact X-Timestamp value.
  • BODY: the exact request body bytes. Empty for GET and for multipart/form-data uploads.
  • Use the secret as-is (UTF-8). Do not decode it.

Test vector — secret sbx_secret_example_do_not_use, GET /v1/payments/accounts?limit=10, timestamp 2026-10-05T14:03:00Z, no body:

77f5c357e886cb42fd92efecd2218b31ad8cd76deba54d20eb58e5bc01f73557
import hashlib, hmac

def sign(secret: str, method: str, path: str, query: str, timestamp: str, body: bytes = b"") -> str:
    msg = f"{method}\n{path}\n{query}\n{timestamp}\n".encode() + body
    return hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
import crypto from "node:crypto";

const sign = (secret, method, path, query, timestamp, body = "") =>
  crypto.createHmac("sha256", secret)
    .update(`${method}\n${path}\n${query}\n${timestamp}\n${body}`)
    .digest("hex");
func sign(secret, method, path, query, timestamp string, body []byte) string {
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(method + "\n" + path + "\n" + query + "\n" + timestamp + "\n"))
	mac.Write(body)
	return hex.EncodeToString(mac.Sum(nil))
}

Serialize the JSON body once and send those exact bytes. A 401 almost always means the signed path, query, body, or timestamp differs from what was sent.


Idempotency

Every POST and PATCH request requires an Idempotency-Key header. Replaying the same key with the same body returns the original response instead of creating a duplicate, so retries after a network failure are safe.

When retrying, reuse the Idempotency-Key but send a new timestamp, nonce, and signature.


Asynchronous Operations

Onboarding, account provisioning, conversions, deposits, and withdrawals settle asynchronously. These endpoints return an initial non-terminal status; retrieve the resource with a GET request until it reaches a terminal state.

ResourceInitial StatusTerminal Statuses
CustomerPENDINGACTIVE (after first onboard), INACTIVE
OnboardingPENDINGACTIVE, FAILED
AccountPENDINGACTIVE, FAILED, SUSPENDED, CLOSED
DepositPENDING / SUBMITTED / PROCESSINGCOMPLETED, FAILED, CANCELLED
ConversionPENDINGCOMPLETED, FAILED
WithdrawalPENDING / SUBMITTED / PROCESSINGCOMPLETED, FAILED, CANCELLED
Limit requestREQUESTEDAPPROVED, REJECTED

A FAILED or CANCELLED deposit or withdrawal may carry a reason_code. Treat that list as open and surface an unrecognised value verbatim.


Error Responses

Errors use a consistent shape. Branch on error_code, which is stable and machine-readable. The message is safe to surface to end users.

{
  "error_code": "VALIDATION_ERROR",
  "message": "currency is required"
}

Build with AI

  • Docs index: https://docs.checker.finance/llms.txt. Append .md to any docs URL for plain markdown.
  • Docs MCP server: https://docs.checker.finance/mcp gives AI assistants endpoint and schema lookup.
{ "mcpServers": { "checker-docs": { "url": "https://docs.checker.finance/mcp" } } }

Add the snippet to ~/.cursor/mcp.json (Cursor), or run claude mcp add --transport http checker-docs https://docs.checker.finance/mcp (Claude Code).




Did this page help you?