Webhooks are the recommended way to consume results from fan-out searches. Instead of polling GET /v1/jobs/:id, you register a webhook on a search and we deliver the complete Envelope to your endpoint when each child job reaches a terminal state.
One child is created per surface × region. You receive one webhook delivery per child, each carrying that child’s full Envelope. Setting a webhook forces the async (202) path, even for a single surface.

Register a webhook

Add a webhook object to any POST /v1/search body.
webhook.url
string
required
The endpoint that receives deliveries. The destination is SSRF-guarded: only http/https schemes are allowed, and private, loopback, link-local, and metadata addresses are rejected at submission (and re-checked at delivery). Use a public HTTPS URL.
webhook.secret
string
The shared secret used to sign each delivery with HMAC-SHA256. If you omit it, deliveries are signed with your account’s active managed signing secret (whsec_…, created in the dashboard). Store the secret somewhere your receiver can read it, and never expose it client-side.
Managed signing secrets are created and rotated in the dashboard. The API exposes a read-only metadata list at GET /v1/webhooks/secrets (never the secret values) so you can check which secrets are active.

Delivery payload

Each delivery is an HTTP POST with a JSON body shaped like this:
Delivery body
id
string
Event id, derived from the child’s deterministic call id (evt_call_…). A re-delivery of the same child result reuses the same id — deduplicate on it.
type
string
job.<status> — the child’s terminal status, e.g. job.completed, job.partial, or job.failed.
createdAt
string
ISO-8601 timestamp of when the event was generated.
job
object
The child’s job header: id, query, surface, region, status, warnings, requestedAt, and completedAt.
credits
object
Success-only billing for this child: { creditsToCharge, creditsCharged }. Present on the top-level payload and again inside result.
result
object
The full Envelope for this child — job, provenance, answer, evidence, an optional html proof-of-page URL (only when the submit opted in with include.html: true on a consumer-UI surface), and credits — the same body returned by GET /v1/jobs/:childId.
A child can complete with nothing to report: if the surface returned no answer, the Envelope carries provenance.surfacePresent: false, a surface_absent warning, and an empty answer. The delivery type is still job.completed. Handle this as a normal, successful outcome.

Verify the signature

Every signed delivery carries two headers:
X-AISearch-Timestamp
string
Unix time in seconds at which the delivery was signed. Use it to reject stale or replayed deliveries.
X-AISearch-Signature
string
HMAC-SHA256(secret, "${timestamp}.${body}"), encoded as lowercase hex.
The signed string is not the body alone: it is the timestamp, a literal ., then the exact raw request body bytes — ${timestamp}.${body}. Binding the timestamp into the signature is what lets you detect replays.
Capture the raw body bytes before parsing JSON, then recompute the HMAC over ${timestamp}.${rawBody}. Re-serializing the parsed object (key reordering, whitespace) changes the bytes and breaks verification. Bound the timestamp skew — reject anything older than ~5 minutes — and always compare with a timing-safe function.
To verify a delivery:
1

Read both headers

Grab X-AISearch-Timestamp and X-AISearch-Signature. Reject the request if either is missing.
2

Bound the timestamp skew

Reject if timestamp is not a number or differs from your current clock by more than ~300 seconds. This is your replay defense.
3

Recompute and compare

Compute HMAC-SHA256(secret, "${timestamp}.${rawBody}") as lowercase hex and compare it against X-AISearch-Signature with a timing-safe equality.

A worked example

With this exact secret, timestamp, and body, the signed string is ${timestamp}.${body} and you must compute this exact signature — use it to unit-test your verifier:
Reproduce it
Sign the exact received bytes. In real deliveries the body is a full event carrying the Envelope, not this two-field sample — any re-serialization changes the hex and fails verification.
Verify the signature yourself in a few lines. Prepend ${timestamp}. to the raw request bytes, recompute the HMAC, and compare it against the X-AISearch-Signature header:

Delivery semantics

1

Return 2xx quickly

Respond as soon as you have verified and accepted the delivery. Do the heavy work — storing the Envelope, updating your records — asynchronously. A non-2xx response (or a timeout) counts as a failed attempt and is retried.
2

Treat deliveries as at-least-once

Deliveries are pushed from a durable workflow step, so the same event id can arrive more than once — after a retry, or if your 2xx was lost on the wire. Deduplicate on the event id and make your handler idempotent so a repeat delivery is a no-op.
3

Expect retries with backoff

A failed attempt (network error, non-2xx, or timeout) is re-run by the durable engine with exponential backoff across several attempts. Because retries reuse the same event id, your dedupe key covers them automatically.
4

Don't rely on retries as your only path

The result is always persisted regardless of delivery. If every attempt is exhausted (for example your endpoint stayed down), reconcile by reading GET /v1/jobs/:childId — the webhook payload’s result is exactly that body, including its credits.
The delivery destination is validated for SSRF on every attempt: only http/https URLs are permitted, and requests to private, loopback, and link-local addresses are refused. Point your webhook at a publicly reachable HTTPS endpoint.

Watch run deliveries

A Watch with a webhookUrl receives the same signed delivery for every surface × region on each dispatched run. Event ids, deduplication, retries, signature verification, and payload semantics are identical to any other async job.

The Envelope

The full result shape delivered in result.

Job lifecycle

When jobs reach the terminal states that trigger deliveries.

POST /v1/search

Submit a search and attach a webhook.

Watches

Schedule searches with the same signed delivery semantics.