Webhooks

Signed status callbacks

Skip polling. We POST the full order to your URL every time its status changes, signed so you can verify it's really us.

Optional — you can always just poll GET /order/{id}. Webhooks are the efficient path once you're past testing.

1. Register your URL

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).

2. The payload

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).

3. Verify the signature

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.

Node.js (Express)

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);
});

Python (Flask)

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

4. Delivery & retries

Always verify the signature before trusting a payload, and re-read the order via GET /order/{id} before releasing anything of value. Never act on an unverified webhook.
← Authentication Errors & limits →