Webhooks
How you find out money arrived.
Events
| Event | Meaning |
|---|---|
payment.confirmed | Paid in full and settled |
payment.underpaid | Something arrived, less than invoiced |
payment.overpaid | More than invoiced arrived |
payment.expired | Window closed with nothing received |
payment.failed | Settlement reverted; funds recoverable |
Only payment.confirmed means you have been paid.
Payload
{
"event": "payment.confirmed",
"created": 1784483382,
"data": {
"id": "0x7f3a...",
"status": "confirmed",
"amount": "25.00",
"amount_received": "25.00",
"currency": "USDC",
"reference": "ORD-1234",
"metadata": { "orderId": "1234" },
"tx_hash": "0x40ec...",
"path": "address",
"settled_at": "2026-07-20T11:04:00.000Z"
}
}
Verifying
X-Specie-Signature: t=1784483382,v1=5257a869...
HMAC-SHA256 over {timestamp}.{raw body}, keyed with your endpoint secret. The timestamp is inside the signed material, so rewriting it invalidates the signature rather than extending its life.
Use the SDK, which does the constant-time comparison and the replay window for you. Verifying by hand:
import { createHmac, timingSafeEqual } from "node:crypto";
const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) throw new Error("Replay");
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
if (!timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(v1, "hex"))) {
throw new Error("Bad signature");
}
Three things that are not optional: the raw body, a constant-time comparison, and the timestamp window. Skip the window and a captured request stays replayable forever.
Retries
Failures retry with backoff — roughly 10s, 1m, 5m, 30m, 2h, 6h, then a day — before being abandoned. Anything outside 2xx counts as a failure.
Every attempt is in Dashboard → Webhooks, and can be replayed by hand.
Make your handler idempotent
The same event can arrive twice: retries, replays, and at-least-once delivery all cause it. Key on data.id and make repeat deliveries a no-op.
Requirements
Endpoints must be https and publicly resolvable. Private, loopback, link-local and cloud metadata addresses are refused, at registration and again at delivery — see security.
Respond 2xx quickly and do the work asynchronously. Delivery times out after 10 seconds.