Merchant API
The merchant API is what your shop or backend calls: create a payment, send the customer to the hosted checkout, and get a signed webhook when it's paid. Payouts from the hot wallet use the same key.
The machine-readable reference is openapi.yaml (OpenAPI 3.1), rendered at
coldtill.com/docs/api. Import it into Postman, Insomnia or a client generator.
New here? Start with the guide Accept a payment from your shop.
- Base URL: your site URL, for example
https://pay.example.com. - Authentication: an
API-Keyheader with a project API key. Create keys under API keys (project admin, with two-factor). Keys are shown once and stored hashed. - Format: JSON request and response bodies. Amounts are decimal strings.
- Errors: a non-2xx status with
{"error": "message"}and sometimes acode. - Rate limit: 600 requests per minute per client IP. Exceeding it returns
429.
An API key belongs to one project. It can create payments and payouts, but payouts can only go to that project's address book, and large ones wait for an admin. See Payouts.
Payments
Create a payment
POST /api/v1/payment
curl -X POST https://pay.example.com/api/v1/payment \
-H "API-Key: $COLDTILL_API_KEY" -H 'Content-Type: application/json' \
-d '{"customerID":"42","customerEmail":"jane@example.com","amountInUSD":25.00,"invoiceID":"INV-1001"}'
| Field | Required | Description |
|---|---|---|
customerID |
yes | Your ID for the customer. The same ID always gets the same deposit addresses |
amountInUSD |
yes | Amount owed, greater than 0 and at most 10,000,000 |
customerEmail |
no | Stored with the customer |
invoiceID |
no | Your order reference, echoed in webhooks |
network |
no | Restrict the checkout to one network code (see Network codes) |
currency |
no | With network, restrict to one currency, for example USDC |
expire |
no | RFC 3339 expiry time, at least a minute ahead. Defaults to the project's payment expiry (24 hours) |
Response:
{
"host": "https://pay.example.com",
"reference_id": "fd09c7e5-d661-4355-9b04-7b3749b41e2e",
"url": "https://pay.example.com/pay/fd09c7e5-d661-4355-9b04-7b3749b41e2e"
}
Send the customer to url. A customer has at most one open payment: creating a new one replaces the previous
one (REPLACED). If the previous payment has a deposit that is still confirming, the request fails with 409
so that deposit isn't stranded; retry after it confirms.
Get a payment
GET /api/v1/payment/reference/{reference_id}
{
"invoiceID": "INV-1001",
"customerID": "42",
"amountInUSD": "25",
"filledAmountInUSD": "25.00000000",
"pendingAmountInUSD": "0",
"paymentState": "FILLED",
"referenceID": "fd09c7e5-…",
"url": "https://pay.example.com/pay/fd09c7e5-…",
"createdAt": "2026-10-10T15:58:23Z",
"expiresAt": "2026-10-11T15:58:23Z",
"deposits": [
{
"network": "BASE", "currency": "USDC", "amount": "25", "amountInUSD": "25",
"transactionHash": "0x…", "status": "confirmed", "confirmations": 12, "requiredConfirmations": 10
}
]
}
Payment states:
| State | Meaning | Final |
|---|---|---|
OPEN |
Nothing confirmed yet | no |
PARTIALLY_FILLED |
Confirmed amount is below 99% of the amount owed | no |
FILLED |
Confirmed amount is within 1% of the amount owed | yes |
OVER_FILLED |
Confirmed amount is more than 1% above the amount owed | yes |
CANCELLED |
Expired before being filled | yes |
REPLACED |
Superseded by a newer payment for the same customer | yes |
Ship the order on FILLED or OVER_FILLED. Prefer webhooks to polling.
Payable currencies
GET /api/v1/blockchain-currency/reference/{reference_id} lists what the customer can pay with for this
payment, with the price used for each.
Deposit address
POST /api/v1/deposit-address/reference/{reference_id} with {"blockchain_code": "BASE"} returns the
customer's permanent address for that network's family. Use it to build your own checkout instead of the
hosted one.
Ticker
GET /api/v1/ticker (no API key) lists enabled networks and tokens with USD prices:
blockchainCode, currencyCode, tokenAddress, standard, walletPrecision, family, price.
Payouts
Create a payout
POST /api/v1/withdrawal/merchant with an Idempotency-Key header:
curl -X POST https://pay.example.com/api/v1/withdrawal/merchant \
-H "API-Key: $COLDTILL_API_KEY" -H 'Content-Type: application/json' \
-H 'Idempotency-Key: refund-INV-1001' \
-d '{"blockchainCode":"BASE","currencyCode":"USDC","amount":"12.50","toAddress":"0xAbC…"}'
| Field | Description |
|---|---|
blockchainCode |
Network code: an EVM network or SOL |
currencyCode |
ETH, USDC, USDT, SOL… as enabled on that network |
amount |
Decimal amount in the currency, at most the token's decimals |
toAddress or addressBookId |
Destination. Through the API it must be in the project's address book |
Retrying with the same Idempotency-Key and the same parameters returns the original payout. Reusing a key
with different parameters returns 409.
The response is the payout (see Payout object) with status pending or pending-approval.
List and get payouts
GET /api/v1/withdrawal/merchant?limit=50&offset=0lists payouts created through the API.GET /api/v1/withdrawal/{id}/merchantreturns one payout.
Payout object
{
"id": "5d1c…", "status": "sent", "network": "BASE", "currency": "USDC",
"tokenContract": "0x8335…", "amount": "12.5", "amountInUSD": "12.50", "toAddress": "0xAbC…",
"transactionHash": "0x…", "blockNumber": 21234567, "idempotencyKey": "refund-INV-1001",
"createdBy": "system", "createdAt": "…", "updatedAt": "…"
}
approvalReason explains why a payout is held; failureReason explains a failure. Statuses are listed in
Payouts.
Network codes
| Code | Mainnet | Testnet mode |
|---|---|---|
ETH |
Ethereum | Sepolia |
BASE |
Base | Base Sepolia |
POLYGON |
Polygon | Amoy |
BSC |
BNB Smart Chain | BSC testnet |
TRON |
Tron | Nile |
SOL |
Solana | Devnet |
BTC |
Bitcoin | Signet |
SDK
sdk/typescript wraps these endpoints and verifies webhooks, with no dependencies:
import { Coldtill, verifyWebhookRequest, isPaid } from '@coldtill/sdk'
const kh = new Coldtill({ baseUrl: 'https://pay.example.com', apiKey: process.env.COLDTILL_API_KEY! })
const { url } = await kh.createPayment({ customerID: '42', amountInUSD: 25, invoiceID: 'INV-1001' })
The WooCommerce plugin adds Coldtill as a payment method for USD stores.