API docs

Sandbox first. Live keys stay locked until ECCB authorization. Money-moving POSTs require an Idempotency-Key UUID.

Quickstart — payment link

curl -X POST "$RAILS/v1/payment-requests" \
  -H "authorization: Bearer $JWT" \
  -H "content-type: application/json" \
  -d '{
    "kind": "link",
    "merchant_account_id": "<merchant-uuid>",
    "amount": 25.00,
    "reference": "Invoice 12"
  }'

The response includes fee, net, and link_path (/l/:slug). Hosted checkout is wallet-only in v1.

v1 map

  • POST /v1/accounts — customer, merchant, or agent wallet
  • GET /v1/accounts/me · GET /v1/accounts/:id/balance
  • POST /v1/payment-requests · POST /v1/payment-requests/:id/pay
  • GET /v1/fees/current — public schedule
  • POST /v1/redemptions — subtype=standard|instant
  • GET /v1/webhook-endpoints · POST /v1/webhooks/deliver

Webhook signatures

Header format t=<unix>,v1=<hex>. Compute HMAC-SHA256 of timestamp + "." + raw body with the endpoint secret. Reject if the timestamp is older than 5 minutes.

const crypto = require("crypto");
function verify(secret, header, payload) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${payload}`)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(parts.v1),
    Buffer.from(expected),
  );
}