Signature verification
How each source proves its events are genuine, and what happens when a signature does not check out.
These pages describe ShellOrbit as it runs today. Breaking changes to the HTTP API are announced before they ship, and existing request and response shapes stay supported.
Every source has a secret. The sender uses it to sign each request, or sends it in a header, and ShellOrbit checks it on the original bytes before any rule reads the event. A forged event cannot raise or hide a finding.
Formats
| Format | Used by | What is checked |
|---|---|---|
| GitHub | GitHub sources | X-Hub-Signature-256: HMAC SHA256 of the body |
| ShellOrbit | Your app, and your own systems | ShellOrbit-Signature: t=<unix>,v1=<hex>: HMAC SHA256 of the timestamp, a dot, and the body, within five minutes |
| Stripe | Stripe | Stripe-Signature, the same scheme as ShellOrbit, within five minutes |
| Shopify | Shopify | X-Shopify-Hmac-Sha256: base64 HMAC SHA256 of the body |
| Paddle | Paddle | Paddle-Signature: ts and h1, within five seconds |
| Standard Webhooks | Svix, Clerk, Resend, Dodo Payments, and others | webhook-id, webhook-timestamp, webhook-signature, or the svix- versions, within five minutes |
| Header secret | AWS EventBridge, Okta, Auth0 | The header the source page names must carry the source secret |
Sign your own events
The ShellOrbit format, in Node.js:
import crypto from "node:crypto";
const body = JSON.stringify(event);
const t = Math.floor(Date.now() / 1000);
const v1 = crypto.createHmac("sha256", process.env.SOURCE_SECRET)
.update(`${t}.${body}`).digest("hex");
// header: ShellOrbit-Signature: t=${t},v1=${v1}
Sign the exact bytes you send. Re-serialising JSON after signing changes them.
When a check fails
The event is kept and marked as failing, so you can see that someone tried, and it is never run through the rules. The source’s events show the result for every request. The usual causes:
- The secret in the vendor and in ShellOrbit differ. Paste it again, or rotate it on the source and update the vendor.
- The body was changed after signing, often by a proxy that re-encodes JSON.
- The clock on your own sender is off by more than five minutes.
Rotate a secret
Rotate it from the source’s page; the new secret is shown once. Update the vendor straight after: requests signed with the old secret fail from then on.
IP allowlisting
On Business, limit a source to a sender’s published address ranges, such as GitHub’s hook addresses. Requests from anywhere else get 403 and are neither stored nor counted.