StableDeliver

API & Webhooks

Create non-custodial crypto checkout orders from your own site, read their status, and receive signed webhook events when a buyer pays. Base URL: https://stabledeliver.com/api/v1

Authentication

Send your API key as a Bearer token. Create keys in your dashboard under Developers. Keys are shown once — store them securely. Live keys start sd_live_, test keys sd_test_. The key resolves your account server-side; you never pass a seller id.

Authorization: Bearer sd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Test mode

A sd_test_ key creates test orders you can complete with no chain calls and no money, so you can build and verify your integration end to end. Test orders are flagged and excluded from your live stats.

# create a test order, then complete it — no USDT required
curl -s https://stabledeliver.com/api/v1/orders/ORDER_ID/simulate_payment \
  -X POST -H "Authorization: Bearer sd_test_..."

Create an order

POST /api/v1/orders. Pass a product_id you own. Optional: external_id (your reference), return_url. Send an Idempotency-Key header — retrying with the same key returns the same order, never a second one. The response includes a checkout_url to send the buyer to.

curl -s https://stabledeliver.com/api/v1/orders \
  -X POST -H "Authorization: Bearer sd_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-4815" \
  -d '{"product_id":"<uuid>","external_id":"wc_1023","return_url":"https://yoursite.com/thanks"}'

# 200 OK
{ "id":"ab12…","status":"awaiting_payment","mode":"live","amount":"25","asset":"USDT",
  "chain":"tron","address":"T…","checkout_url":"https://stabledeliver.com/c/<uuid>",
  "external_id":"wc_1023" }

Read an order & list products

GET /api/v1/orders/:id        # -> the order (your account only)
GET /api/v1/products          # -> { "data": [ { id, name, price, asset, chain, ... } ] }

Webhooks

Add an HTTPS endpoint under Developers. We POST these events: order.created, order.verified, order.delivered, order.expired. Each request carries a stable X-StableDeliver-Event-Id — dedupe on it (retries reuse the same id). Payloads never contain license keys, delivery contents, or auth tokens.

POST (your endpoint)
X-StableDeliver-Event-Id: evt_9f8c…
X-StableDeliver-Signature: t=1720000000,v1=6b3a…hex

{ "id":"evt_9f8c…","type":"order.verified","created":1720000000,
  "data": { "order": { "id":"ab12…","status":"verified","amount":"25",
    "asset":"USDT","chain":"tron","external_id":"wc_1023" } } }

Verify the signature

Recompute HMAC_SHA256(secret, "<t>.<raw body>") and compare in constant time. Reject if |now − t| > 5 minutes (replay protection). Use the raw request body, not a re-serialized object.

Node.js

import crypto from "crypto";
function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now()/1000 - t) > 300) return false;   // 5-min window
  const expected = crypto.createHmac("sha256", secret).update(t + "." + rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

PHP

function sd_verify($rawBody, $header, $secret) {
  parse_str(str_replace(',', '&', $header), $p);   // t=...&v1=...
  if (empty($p['t']) || abs(time() - (int)$p['t']) > 300) return false;
  $expected = hash_hmac('sha256', $p['t'] . '.' . $rawBody, $secret);
  return hash_equals($expected, $p['v1']);
}

Errors

JSON { "error": "code", "message": "..." }. Common codes:

401 unauthorizedMissing/invalid/revoked key
429 rate_limitedToo many requests for this key
400 product_id_requiredNo product_id in the body
404 product_not_found / order_not_foundNot found in your account
409 product_inactive / no_receiving_walletProduct off, or no wallet for its chain
403 test_key_requiredsimulate_payment needs a test key

Non-custodial: payments go straight to your own wallet and are verified on-chain. StableDeliver never holds funds and never has your keys.