Developers

Settle API reference

One endpoint creates a hosted stablecoin checkout. Poll it, verify settlements on-chain, and receive HMAC-signed webhooks when invoices are paid. Base URL is this site's domain — every route below is relative to it.

Authentication

Public endpoints authenticate with your publishable key (pk_live_… or pk_…), found in Dashboard → Developers. The secret key (sk_live_…) is reserved for future server-side endpoints — never expose it in a browser or mobile app.

passing the key
POST /api/checkout
{
  "api_key": "pk_live_…",
  "amount": 10
}

Create a checkout

POST/api/checkout

Creates a pending invoice and returns a hosted checkout URL plus the exact on-chain payment details. Bill either a custom amount or a plan_id from Dashboard → Plans.

request
{
  "api_key":    "pk_live_…",
  "amount":     49.00,              // or "plan_id": "uuid"
  "currency":   "USDC",             // USDC | USDT | DAI (plan billing uses its own)
  "customer_email": "[email protected]",
  "subscribe":  false,              // true with plan_id → start a subscription
  "discount_code": "LAUNCH20"       // optional, validated + redeemed
}
response
{
  "id": "3f9c…",                      // invoice id
  "url": "https://your-site/checkout/3f9c…",
  "pay_to": "0x7A9c…3F21",             // merchant wallet (funds go here directly)
  "chain": "base",
  "currency": "USDC",
  "amount": 49.000371,                 // EXACT on-chain amount to send
  "nominal_amount": 49,
  "expires_at": "2026-09-08T18:04:00Z",
  "test_mode": false
}

The customer must send exactly amount of the chain's token contract to pay_to. The sub-cent delta in the amount is how Settle matches the transfer to this invoice — never round it.

Check status

GET/api/checkout/status?id={invoice_id}

Returns the invoice status (pending, paid, expired, …) and, when paid, the transaction hash. Each poll also runs the log-scan settlement fallback, so QR payments settle even without webhooks — the hosted checkout polls this every 4 seconds.

Confirm a wallet payment

POST/api/checkout/confirm

After a wallet payment mines, submit the transaction hash. Settle fetches the receipt on-chain and requires an ERC-20 Transfer matching the invoice's exact amount, token, and recipient — an unrelated hash settles nothing.

request
{
  "invoice_id": "3f9c…",
  "tx_hash": "0x9f2c…d41a"
}

Idempotency

Send an Idempotency-Key header (any unique string, e.g. your order id) with POST /api/checkout. Replaying the same key returns the original invoice instead of creating a duplicate — safe for retries, timeouts, and double-clicks.

curl
curl -X POST https://your-site/api/checkout \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1234" \
  -d '{"api_key":"pk_live_…","amount":49}'

Test mode

Use your test key (pk_test_…) to create checkouts flagged test_mode. They run the exact same flow but are excluded from your analytics, and the payment simulator stays available for them in production. Roll the test key any time from Dashboard → Developers.

Webhooks

Register HTTPS endpoints in Dashboard → Developers. Every event is HMAC-SHA256-signed in the X-Settle-Signature header (hex digest of the raw body, using the endpoint's whsec_… secret). Failed deliveries retry with backoff for up to 12 attempts and can be replayed manually from the event log.

invoice.paidinvoice.expiredinvoice.disputeddispute.message
payload
{
  "event": "invoice.paid",
  "created": 1789012345,
  "data": {
    "invoice_id": "3f9c…",
    "amount": 49.000371,
    "currency": "USDC",
    "chain": "base",
    "tx_hash": "0x9f2c…d41a",
    "payer": "0x4B1e…8C07"
  }
}
verify (Node.js)
import crypto from "node:crypto";

const expected = crypto
  .createHmac("sha256", process.env.SETTLE_WEBHOOK_SECRET)
  .update(rawBody)                 // the RAW request body string
  .digest("hex");

if (expected === req.headers["x-settle-signature"]) {
  const { event, data } = JSON.parse(rawBody);
}
verify (Python)
import hmac, hashlib

expected = hmac.new(
    SECRET.encode(), raw_body, hashlib.sha256
).hexdigest()

if hmac.compare_digest(expected, request.headers["X-Settle-Signature"]):
    payload = json.loads(raw_body)

Errors & limits

StatusMeaning
400Invalid request — see the error field
401Invalid or missing api_key
403Simulator disabled, or viewers attempting writes
404Unknown invoice / plan / product
409Merchant not configured, or slug already taken
429Rate limited — slow down (Retry-After header)

Public endpoints are rate limited per IP / key (60 checkouts per minute per key) to protect the unique-amount space.

Ready to build?

Create an account, grab your keys, and run your first checkout in under five minutes — 0% fees during launch.

Start free