kemBusiness
PlatformProductHow it worksPayrollDevelopersFAQ
Sign inGet started↗

Developer documentation

Everything you need to take USDT payments with the Kem Business API: authentication, the payments endpoints, and signed webhooks that drive your order state.

AuthenticationPaymentsErrors & limitsWebhooksTesting

Authentication

Create API keys in the dashboard under API keys, and send the secret key as a Bearer token on every request. Test keys (sk_test_…) create test payments; live keys (sk_live_…) move real money.

Authorization: Bearer sk_live_...   # or sk_test_...
Base URL: https://api-business.kemapp.io

Payments

Create a payment and send your customer to its hosted checkout_url — Kem handles wallets, networks and confirmations. Track state by fetching the payment or, better, through webhooks.

POST/v1/payments
curl -X POST https://api-business.kemapp.io/v1/payments \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042" \
  -d '{
    "amount": "25.000000",
    "currency": "USDT",
    "merchant_reference": "order-1042",
    "customer_email": "[email protected]",
    "description": "Pro plan",
    "return_url": "https://yourshop.example/thanks",
    "metadata": {"order_id": "1042"}
  }'
{
  "id": "pay_01J...",
  "status": "pending",
  "mode": "test",
  "amount": "25.000000",
  "amount_minor": 25000000,
  "currency": "USDT",
  "merchant_reference": "order-1042",
  "checkout_url": "https://pay.kemapp.io/pay_01J...",
  "checkout_expires_at": "2026-07-13T13:30:00+00:00",
  "created_at": "2026-07-13T13:00:00+00:00"
}

Idempotency-Key — optional but recommended — a retry with the same key returns the original payment instead of creating a duplicate.

GET/v1/payments— query: status, limit (1–100, default 20), cursor · returns has_more, next_cursor
GET/v1/payments/{id}

amount — decimal string with 6 places ("25.000000"); amount_minor — the same value as an integer in micro-USDT.

Payment lifecycle

pending → detected → succeeded · failed · expired

pending — awaiting the transfer · detected — seen on-chain, confirming · succeeded — funds confirmed in your wallet (fulfill on this one) · failed / expired — terminal. Refund transitions arrive as webhook events.

checkout window: 30 minutes, then → expired

Errors & limits

Every error uses one envelope. Codes are stable — branch on error.code, never on the message.

HTTP 4xx/5xx
{
  "error": {
    "code": "idempotency_conflict",
    "message": "Idempotency-Key reused with a different request"
  }
}
401 unauthorizedmissing or invalid API key
404 not_foundunknown payment id (or a live id with a test key)
409 idempotency_conflictIdempotency-Key reused with a different body — identical retries replay the original response
422 (validation)malformed body — bad amount, unsupported currency
429 rate_limitedover the per-key budget: 100 writes/min, 1000 reads/min

Products, payment links, webhook endpoints and refunds are managed from the dashboard — they don't have public REST endpoints yet.

Webhooks

Add an endpoint in the dashboard under Webhooks and choose the events you care about. Each endpoint gets its own whsec_… signing secret, shown once at creation.

payment.createdA payment intent was created (API or checkout link).
payment.detectedThe transfer was seen on-chain / in the KEM ledger; confirming.
payment.succeededFunds are confirmed in your business wallet. Fulfill on this event.
payment.failedThe payment failed.
payment.expiredThe checkout window closed without payment.
payment.refund.createdA refund was initiated from the dashboard.
payment.refund.succeededThe refund settled back to the customer.

Event payload

{
  "id": "payment_event_01J...",
  "type": "payment.succeeded",
  "created": "2026-07-13T13:04:12+00:00",
  "data": {
    "payment": {
      "id": "pay_01J...",
      "status": "succeeded",
      "amount_minor": 25000000,
      "currency": "USDT",
      "merchant_reference": "order-1042",
      "network": "TRON",
      "product_id": null,
      "payment_link_id": null
    }
  }
}

Verifying signatures

Every delivery carries a Kem-Signature header. Recompute the HMAC over "<t>." plus the raw request body with your endpoint secret, and compare in constant time. Reject anything that doesn't match.

Kem-Signature: t=<unix_timestamp>,v1=<hmac_sha256_hex>

signed_input = "<t>." + raw_request_body
v1 = HMAC_SHA256(endpoint_secret, signed_input)   # secret: whsec_...

# reject replays: drop the delivery when |now - t| > 300s
import crypto from "node:crypto";

export function verifyKemSignature(rawBody, header, secret) {
  // header: "t=1720000000,v1=hex"
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto
    .createHmac("sha256", secret)               // secret: whsec_...
    .update(`${parts.t}.`)
    .update(rawBody)                            // the RAW request bytes
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
import hashlib, hmac

def verify_kem_signature(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=") for p in header.split(","))
    expected = hmac.new(
        secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Delivery & retries

Answer with a 2xx quickly. Failed deliveries are retried with backoff, then dead-lettered:

1m · 5m · 30m · 2h · 12h · 24h

Testing

With a test key you can drive a payment through its whole lifecycle without moving money — simulate transitions and watch your webhooks fire:

curl -X POST https://api-business.kemapp.io/v1/payments/{id}/_simulate/succeeded \
  -H "Authorization: Bearer sk_test_..."
BUILT IN THE GULF · SETTLED IN USDT ·
kemBusiness

Get paid
in stablecoins.

Open the dashboard and take your first USDT payment today — or walk through it with us on a 20-minute call.

Open the dashboard↗Book a call↗

Already on kem? Sign in

Product

  • Sign in
  • Create account
  • Book a demo

Kem for everyone

  • Consumer app
  • iOS — App Store
  • Android — Google Play

Company

  • Tether investment
  • Privacy policy
  • Terms & conditions

© 2026 Kem · Kem operates through KEM BAHRAIN W.L.L (CR No. 1-195589), a licensed Crypto-Asset Services Provider (CASP) registered in Bahrain. [email protected]

Built in the Gulf.