Webhooks
Register a callbackUrl and receive a signed project.completed event instead of polling.
Pass callbackUrl to POST /v1/search and Orbit POSTs a signed project.completed event to it once, when the search finishes — including a search that found nobody — or reaches the target count in your brief. A brief that ends in awaiting_input sends no event; submit a sharper brief.
Registering
curl -X POST https://api.orbitpeople.ai/v1/search \
-H "Authorization: Bearer $ORBIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"brief": "...",
"callbackUrl": "https://yourapp.com/webhooks/orbit"
}'The callbackUrl must be a public http(s) URL — use https. A private or localhost address is accepted but never called, so use a public tunnel when testing locally. It's per request: each search registers its own callback.
Payload
{
"event": "project.completed",
"projectId": "abc123",
"status": {
"projectId": "abc123",
"status": "done",
"done": true,
"totalResults": 42,
"verifiedResults": 35,
"requestedTarget": null,
"starredResults": 0,
"clarificationQuestion": null,
"error": null
},
"resultsUrl": "https://api.orbitpeople.ai/v1/projects/abc123/results"
}status is the same object GET /v1/projects/{projectId} returns. resultsUrl is ready to fetch immediately, with your API key.
Signature verification
Every delivery carries X-Orbit-Signature: sha256=<hex> — an HMAC-SHA256 of the raw request body, keyed with your workspace's webhook secret. Always verify before trusting the payload, and compute it over the bytes you received, not over re-serialized JSON.
The webhook secret (whsec_…) is in Settings → Workspace → API (/settings/workspace/api), under the key list — click to reveal it.
Node.js:
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyOrbitSignature(rawBody: string, header: string, secret: string) {
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
const provided = header.replace(/^sha256=/, "").toLowerCase();
return (
provided.length === expected.length &&
timingSafeEqual(Buffer.from(provided), Buffer.from(expected))
);
}
// rawBody: the request body exactly as received, before any JSON parsing.
// header: the value of the X-Orbit-Signature request header.
const ok = verifyOrbitSignature(
rawBody,
header,
process.env.ORBIT_WEBHOOK_SECRET!,
);
// Reject the delivery (e.g. respond 401) when ok is false.Python:
import hashlib, hmac
def verify_orbit_signature(raw_body: bytes, header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
provided = header.removeprefix("sha256=").lower()
return hmac.compare_digest(provided, expected)Delivery and retries
Respond 2xx as soon as you've received the event, and do the work afterwards. A delivery that fails (non-2xx or unreachable) is tried three times in total — immediately, after 30 seconds, then after 5 minutes. Make your handler idempotent: the same projectId may arrive more than once. If nothing arrives, poll GET /v1/projects/{projectId} instead.