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_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTest 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 unauthorized | Missing/invalid/revoked key |
| 429 rate_limited | Too many requests for this key |
| 400 product_id_required | No product_id in the body |
| 404 product_not_found / order_not_found | Not found in your account |
| 409 product_inactive / no_receiving_wallet | Product off, or no wallet for its chain |
| 403 test_key_required | simulate_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.