Developer platform
Webhooks
Signed event delivery to your HTTPS endpoint.
Register up to 5 webhooks per wallet for any of the event types, optionally filtered to one squad. The signing secret (whsec_…) is shown once at creation and on rotation.
Delivery
BodyJSON
{ "id": "<event id>", "type": "order.filled", "created_at": "…", "squad_id": "…", "data": { … } }
Headers: Wisp-Event-Id, Wisp-Delivery-Id, Wisp-Timestamp and Wisp-Signature: t=<unix>,v1=<hex>, where the hex is HMAC-SHA256 of ${t}.${rawBody} with your secret. data matches the event payload from /events.
- At-least-once; order is not guaranteed. Dedupe on
Wisp-Event-Id. - Success is any 2xx within 10 seconds. Redirects are not followed; at most 1 KB of your response is stored.
- Retries after 1m, 5m, 30m, 2h and 6h (6 attempts). Implemented, not load-tested.
- A webhook is disabled after 10 consecutive deliveries that failed every attempt; the reason is shown on the Developers page.
- Reject a
Wisp-Timestampmore than 5 minutes from your clock. The server doesn't enforce this; your receiver does.
Verify a signature
import crypto from "node:crypto";
// rawBody: the exact request body string. header: the Wisp-Signature value.
export function verifyWisp(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const got = Buffer.from(parts.v1 ?? "", "hex");
const want = Buffer.from(expected, "hex");
return got.length === want.length && crypto.timingSafeEqual(got, want);
}
URL rules
HTTPS only. The hostname is resolved and private, loopback and link-local addresses are rejected, including 169.254.169.254 and IPv6 equivalents.
Last updated October 11, 2026
Wisp runs in a sandbox today: paper trading on live market data. Experimental software, not financial advice.
