Webhooks
Orion sends events to your HTTPS endpoint as JSON POST requests.
{
"id": "evt_4q8r…",
"object": "event",
"type": "payment.succeeded",
"api_version": "2026-10-01",
"created": "2026-10-19T22:58:03.000Z",
"environment": "sandbox",
"data": { "id": "pay_7k2…", "object": "payment", "status": "succeeded", "amount": 12430, "currency": "CAD" }
}
Event types
payment.pending, payment.requires_action, payment.authorized, payment.succeeded, payment.failed, payment.cancelled, payment.expired, refund.completed, refund.failed, dispute.created, dispute.closed, settlement.reported, settlement.updated.
Verifying signatures
Each request includes:
Orion-Signature: t=1760914683,v1=5f2c…
Orion-Event-Id: evt_4q8r…
Orion-Delivery-Attempt: 1
v1 is the hex HMAC-SHA256 of "<t>.<raw body>" using your endpoint's signing secret.
import crypto from "node:crypto";
export function verifyOrion(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=", 2)));
const t = Number(parts.t);
if (!Number.isInteger(t) || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected), b = Buffer.from(parts.v1);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Then:
- Use the raw body — re-serialised JSON will not match.
- De-duplicate by event
id; the same event can arrive more than once. - Events may arrive out of order; read the payment if you need the current state.
- Respond
2xxquickly and process asynchronously.
Retries
Non-2xx responses and timeouts (10 s) are retried with exponential backoff (about 30 s, 2 min, 8 min, 32 min, then hourly), up to 8 attempts. You can retry a delivery from the portal; it reuses the same event ID.
Sandbox receiver
Choose Use Orion's built-in sandbox receiver when adding an endpoint to see verified deliveries in the portal without hosting anything.