Appearance
Webhooks reference
Rampwire POSTs JSON to the callback_url you supplied on POST /api/v1/pay when certain order statuses change. There is no per-integrator signing secret in the database — verification uses the platform WEBHOOK_SECRET environment value your operator provisions (treat it like a shared HMAC key).
Delivery URL: exactly the callback_url saved on the order (HTTPS recommended).
Event model
The JSON body always uses:
json
{
"event": "order.status_changed",
"order_id": 10042,
"status": "fiat_sent",
"timestamp": "2026-05-03T12:45:00.000Z",
"data": {}
}| Field | Meaning |
|---|---|
event | Constant string order.status_changed. |
order_id | Numeric id. |
status | New / current status for this notification (claimed, fiat_sent, confirmed, completed, cancelled, disputed, …). |
timestamp | ISO-8601 time when the webhook was built. |
data | Subset of order columns (see below). |
Logical “events” integrators care about map to status:
| When you hear… | status value |
|---|---|
| LP / system attached liquidity | claimed |
| LP marked fiat sent | fiat_sent |
| Fiat confirmed | confirmed |
| Flow finished successfully | completed |
| User / system cancelled | cancelled |
| Dispute opened | disputed |
Branch on status (and optionally compare to your last seen status) — not on multiple event enum values.
Payload data shape
data contains a whitelist of order fields when present, for example:
id,user_id,lp_id,payee_id,typeamount_fiat,currency,amount_usd,amount_cryptocrypto_symbol,crypto_chain,lp_receive_address,user_crypto_tx_hashspread_bps,fee_usd,status,timeout_atcreated_at,completed_at,cancelled_atcallback_url,receipt_url
Exact keys depend on what was stored on the row.
Signature header
Each request includes:
http
Content-Type: application/json
X-Rampwire-Signature: <lowercase hex>Algorithm: HMAC-SHA256 over the raw request body bytes (the exact JSON string), using WEBHOOK_SECRET as the HMAC key. The header value is the hex-encoded digest (not Base64).
Verification — JavaScript (Node 18+ / Workers)
javascript
import crypto from "node:crypto";
function verifyRampwireWebhook(rawBody, headerHex, secret) {
if (!headerHex || !secret) return false;
const expected = crypto.createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
try {
return crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(headerHex, "hex"));
} catch {
return false;
}
}
// Express-style example
app.post("/hooks/rampwire", express.raw({ type: "application/json" }), (req, res) => {
const raw = req.body; // Buffer
const sig = req.header("x-rampwire-signature") || "";
if (!verifyRampwireWebhook(raw.toString("utf8"), sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).send("bad signature");
}
const payload = JSON.parse(raw.toString("utf8"));
res.sendStatus(200);
});Use the raw body string exactly as received (no re-serialization) before HMAC.
Verification — Python
python
import hmac
import hashlib
def verify_rampwire_webhook(raw_body: bytes, header_hex: str, secret: str) -> bool:
expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header_hex)
# Flask-style
@app.post("/hooks/rampwire")
def hook():
raw = request.get_data(cache=False, as_text=False)
sig = request.headers.get("X-Rampwire-Signature", "")
if not verify_rampwire_webhook(raw, sig, settings.WEBHOOK_SECRET):
return "bad signature", 401
payload = json.loads(raw.decode("utf-8"))
return "", 200Retry policy
Delivery attempts:
- Immediate
- After ~400 ms backoff
- After ~800 ms additional backoff
So up to three tries with exponential backoff (~400 ms base). Failures are logged in webhook_logs server-side. Return 2xx quickly to stop retries.
Quick curl simulation (receiver side)
Your server should echo 200 and verify HMAC:
bash
# Receiver test (local) — use ngrok or similar to expose HTTPS publicly
python -m http.server 9999Production callback_url must be reachable from Rampwire workers.
Security checklist
- Rotate
WEBHOOK_SECRETwith your operator if leaked. - Reject replays using (
order_id,status,timestamp) idempotency in your DB. - Terminate TLS at your edge; do not accept unsigned test traffic in prod.
More context: Agent API guide · Errors