Skip polling. We POST the full order to your URL every time its status changes, signed so you can verify it's really us.
GET /order/{id}. Webhooks are the efficient path once you're past testing.Point us at your endpoint (must be https:// in production). Authenticate with your API key:
curl -X POST https://cryptoexchanger.co/api/portal/keys/webhook \ -H "Authorization: Bearer cx_live_..." \ -H "Content-Type: application/json" \ -d '{"url":"https://yourapp.com/hooks/cx"}'
{ "ok": true, "webhookUrl": "https://yourapp.com/hooks/cx",
"signingSecret": "whsec_..." }
The signingSecret is what you verify signatures with. It's tied to your key (send {"url":""} to turn webhooks off).
Every status change, we POST this JSON — the same order object GET /order/{id} returns:
{ "event": "order.updated",
"order": { "id": "AKCQ8", "status": "finished",
"payoutHash": "0xabc...", "expectedToAmount": 1.5564, ... } }
You'll get one for each transition: waiting → confirming → exchanging → sending → finished (or failed / refunded / expired).
Each request carries a header:
X-CX-Signature: sha256=<hex>
where <hex> = HMAC-SHA256 of the raw request body using your signingSecret. Compute the same and compare in constant time. Reject anything that doesn't match.
const crypto = require("crypto"); app.post("/hooks/cx", express.raw({type:"*/*"}), (req, res) => { const got = req.get("X-CX-Signature") || ""; const want = "sha256=" + crypto.createHmac("sha256", process.env.CX_WEBHOOK_SECRET) .update(req.body).digest("hex"); if (got.length !== want.length || !crypto.timingSafeEqual(Buffer.from(got), Buffer.from(want))) return res.status(401).end(); const { order } = JSON.parse(req.body.toString()); // ... update your DB, mark order[status] ... res.sendStatus(200); });
import hmac, hashlib @app.post("/hooks/cx") def hook(): raw = request.get_data() want = "sha256=" + hmac.new(SECRET.encode(), raw, hashlib.sha256).hexdigest() if not hmac.compare_digest(want, request.headers.get("X-CX-Signature", "")): return "", 401 order = request.get_json()["order"] # ... handle ... return "", 200
2xx within ~8 seconds. Anything else (timeout, 5xx, network error) is a failed delivery.order.id + status).GET /order/{id} before releasing anything of value. Never act on an unverified webhook.