Event Types & Payloads
The webhook event envelope, the full event taxonomy, and per-event data fields with example payloads
Every webhook delivery carries exactly one event. This page documents each event type and the payload it carries. For headers, signature verification, and retry semantics, see the Webhooks overview.
The envelope
All events except monitor.change share one envelope:
{
"id": "evt_a1b2c3…",
"type": "job.completed",
"createdAt": "2026-07-13T10:00:00Z",
"data": { }
}| Field | Type | Description |
|---|---|---|
id | string | Unique event ID (evt_…). |
type | string | The event type — same as the X-Anakin-Event header. |
createdAt | string | When the event was created (ISO 8601). |
data | object | Event-specific fields, documented per event below. |
monitor.change keeps its original flat payload (no envelope) for backward compatibility — see below.
Event taxonomy
| Event | Fires when |
|---|---|
job.completed | An async job finishes successfully — data.jobType tells you which product |
job.failed | An async job fails |
batch.completed | A batch URL-scrape settles successfully (fires once per batch) |
batch.failed | A batch URL-scrape fails as a whole |
wire.job.completed | A Wire task finishes successfully |
wire.job.failed | A Wire task fails |
monitor.change | A Website Monitor detects a change |
ai.search.completed | An AI Visibility search settles with at least one source answered |
ai.search.failed | An AI Visibility search settles with every source failed |
webhook.test | You trigger a test delivery from the dashboard or API |
Registered endpoints receive the events matched by their filter (empty filter = all); a per-request webhook_url receives only the events for that job. Fetch this list programmatically with GET /v1/webhooks/events.
job.completed / job.failed
Fired when an asynchronous job settles. data.jobType distinguishes the product: url_scraper, web_scraper, map, crawl, agentic_search, or search.
data fields
| Field | Type | Description |
|---|---|---|
jobId | string | The job ID returned at submission. |
jobType | string | url_scraper, web_scraper, map, crawl, agentic_search, or search. |
status | string | "completed" or "failed". |
url | string | The submitted URL. |
country | string | Proxy country — present when set on the job. |
cached | boolean | Present when the result was served from cache. |
creditsUsed | number | Credits charged — present when known. |
durationMs | number | Processing time in milliseconds — present when known. |
createdAt | string | When the job was created. |
completedAt | string | When the job settled. |
result_url | string | Always present. The canonical authenticated GET endpoint for the full result — fetch it with your API key. |
result | object | The full canonical result document, inlined only when the whole envelope is ≤ 256 KB. |
resultOmitted | string | "size" or "unavailable" — present when result was dropped. |
error | string | Failure reason — job.failed only. |
Treat the inline result as an optimization: when the result document is small it's included so you never make a second request; when it would push the envelope past 256 KB it's dropped and resultOmitted: "size" is set instead. result_url is always there and always canonical — when in doubt, fetch it:
| Job | result_url |
|---|---|
url_scraper, batch_url_scraper | https://api.anakin.io/v1/url-scraper/{jobId} |
web_scraper | https://api.anakin.io/v1/web-scraper/{jobId} |
map | https://api.anakin.io/v1/map/{jobId} |
crawl | https://api.anakin.io/v1/crawl/{jobId} |
agentic_search | https://api.anakin.io/v1/agentic-search/{jobId} |
search | https://api.anakin.io/v1/request/{jobId} |
Wire tasks (wire.job.*) | https://api.anakin.io/v1/wire/jobs/{jobId} |
Whatever the job type, follow the result_url from the event rather than constructing URLs yourself.
Fetching the complete result —
result_urlis a normal authenticated endpoint:GETit with your API key, exactly like polling. This is the one pattern to build into every receiver, because inlining is best-effort and size-dependent:curl "https://api.anakin.io/v1/url-scraper/0b0e5e7e-…" \ -H "X-API-Key: your_api_key"A receiver that handles
result_urlworks for every event and every result size; a receiver that only reads inlineresultsilently breaks the day a result crosses 256 KB.
job.failed events never inline results — read error, or fetch result_url for the full failure details.
Example — job.completed with the result inlined
{
"id": "evt_5f2a9c…",
"type": "job.completed",
"createdAt": "2026-07-13T10:00:05Z",
"data": {
"jobId": "0b0e5e7e-4c2a-4b8e-9c1d-2f3a4b5c6d7e",
"jobType": "url_scraper",
"status": "completed",
"url": "https://example.com",
"country": "us",
"cached": false,
"creditsUsed": 1,
"durationMs": 5000,
"createdAt": "2026-07-13T10:00:00Z",
"completedAt": "2026-07-13T10:00:05Z",
"result_url": "https://api.anakin.io/v1/url-scraper/0b0e5e7e-4c2a-4b8e-9c1d-2f3a4b5c6d7e",
"result": {
"id": "0b0e5e7e-4c2a-4b8e-9c1d-2f3a4b5c6d7e",
"status": "completed",
"url": "https://example.com",
"jobType": "url_scraper",
"country": "us",
"html": "<html>...</html>",
"cleanedHtml": "<div>...</div>",
"markdown": "# Page content...",
"cached": false,
"error": null,
"createdAt": "2026-07-13T10:00:00Z",
"completedAt": "2026-07-13T10:00:05Z",
"durationMs": 5000
}
}
}result is the same document the poll endpoint returns — here, the shape of GET /v1/url-scraper/{id}.
Example — job.completed with the result omitted
A multi-page crawl result easily exceeds the envelope limit, so the event arrives reference-only:
{
"id": "evt_8d41e0…",
"type": "job.completed",
"createdAt": "2026-07-13T10:04:41Z",
"data": {
"jobId": "9f8e7d6c-5b4a-4c3d-8e2f-1a0b9c8d7e6f",
"jobType": "crawl",
"status": "completed",
"url": "https://example.com",
"country": "us",
"creditsUsed": 20,
"durationMs": 281000,
"createdAt": "2026-07-13T10:00:00Z",
"completedAt": "2026-07-13T10:04:41Z",
"result_url": "https://api.anakin.io/v1/crawl/9f8e7d6c-5b4a-4c3d-8e2f-1a0b9c8d7e6f",
"resultOmitted": "size"
}
}Example — job.failed
{
"id": "evt_c77b12…",
"type": "job.failed",
"createdAt": "2026-07-13T10:02:11Z",
"data": {
"jobId": "0b0e5e7e-4c2a-4b8e-9c1d-2f3a4b5c6d7e",
"jobType": "url_scraper",
"status": "failed",
"url": "https://example.com",
"country": "us",
"createdAt": "2026-07-13T10:00:00Z",
"completedAt": "2026-07-13T10:02:11Z",
"result_url": "https://api.anakin.io/v1/url-scraper/0b0e5e7e-4c2a-4b8e-9c1d-2f3a4b5c6d7e",
"error": "Connection timeout"
}
}Sync endpoints emit these too —
POST /v1/searchandPOST /v1/url-scraper/scrapereturn results inline in the HTTP response, but registered endpoints subscribed tojob.completed/job.failedstill receive the events, so a single receiver observes every job.
batch.completed / batch.failed
Fired once per batch when the whole batch scrape settles — the individual URLs inside a batch do not fire job.* events. Per-URL failures in an otherwise settled batch show up in the failed count on batch.completed; batch.failed fires when the batch as a whole fails.
data fields
| Field | Type | Description |
|---|---|---|
jobId | string | The batch job ID returned at submission. |
jobType | string | Always batch_url_scraper. |
status | string | "completed" or "failed". |
urlsTotal | number | URLs submitted in the batch. |
completed | number | URLs that scraped successfully. |
failed | number | URLs that failed. |
creditsUsed | number | Credits charged for the batch. |
result_url | string | GET endpoint for the full batch result with per-URL outcomes. |
Batch events never inline results — fetch result_url for the per-URL outcomes.
Example — batch.completed
{
"id": "evt_2b90fe…",
"type": "batch.completed",
"createdAt": "2026-07-13T10:00:12Z",
"data": {
"jobId": "a28d0097-6828-4a2f-a242-dd5658656771",
"jobType": "batch_url_scraper",
"status": "completed",
"urlsTotal": 3,
"completed": 2,
"failed": 1,
"creditsUsed": 2,
"result_url": "https://api.anakin.io/v1/url-scraper/a28d0097-6828-4a2f-a242-dd5658656771"
}
}wire.job.completed / wire.job.failed
Fired when a Wire task settles — pass webhook_url at submission, or subscribe a registered endpoint. Delivered within ~10 seconds of completion.
data fields
| Field | Type | Description |
|---|---|---|
jobId | string | The Wire job ID. |
actionId | string | The action that ran (e.g. li_profile_scrape). |
actionName | string | Human-readable action name. |
catalogSlug | string | The catalog the action belongs to. |
status | string | "completed" or "failed". |
creditsUsed | number | Credits charged. |
durationMs | number | Execution time in milliseconds. |
error | string | Failure reason — wire.job.failed only. |
result_url | string | Always present — https://api.anakin.io/v1/wire/jobs/{jobId}. |
result | object | The full job result (same shape as GET /v1/wire/jobs/{id}), inlined when small — the same 256 KB envelope rule applies. |
Example — wire.job.completed
{
"id": "evt_77aa0d…",
"type": "wire.job.completed",
"createdAt": "2026-07-13T10:00:09Z",
"data": {
"jobId": "7c3f1a2b-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"actionId": "li_profile_scrape",
"actionName": "LinkedIn Profile Scrape",
"catalogSlug": "linkedin",
"status": "completed",
"creditsUsed": 2,
"durationMs": 8420,
"result_url": "https://api.anakin.io/v1/wire/jobs/7c3f1a2b-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"result": {
"status": "completed",
"data": {
"name": "Jane Doe",
"headline": "Software Engineer at Acme Corp",
"location": "San Francisco, CA",
"connections": 500
},
"credits_used": 2,
"execution_ms": 8420
}
}
}On wire.job.failed, error is present and no result is inlined.
monitor.change
Website-monitoring alerts keep their original flat payload — type at the top level, no envelope — so existing receivers keep working unchanged:
{
"type": "monitor.change",
"monitorId": "62e555fb-…",
"url": "https://example.com/product/123",
"watchMode": "specific_data",
"changeId": "c1e8fbac-…",
"changedAt": "2026-07-13T10:00:00Z",
"changedFields": ["price"],
"summary": "The price dropped from $19.99 to $14.99.",
"diff": { "before": { "price": 19.99 }, "after": { "price": 14.99 } }
}Registered endpoints subscribed to monitor.change receive these deliveries too, in this same flat shape. For the full payload schema (watchMode variants, diff, AI summaries), fetching before/after snapshots, and monitor-specific setup, see Website Monitor Webhooks.
ai.search.completed / ai.search.failed
Fired when an AI Visibility search settles — every selected source has either answered or failed. ai.search.completed means at least one source answered; ai.search.failed means all of them failed.
Note — full answers are never inlined. Unlike
job.*andwire.job.*events, AI Visibility deliveries don't carry aresultobject at all: each source's answer can run to hundreds of KB, so the event inlines only a bounded per-sourcesummary(≤ 500 chars). To get the complete answers,GETtheresult_urlwith your API key — it is always present.
data fields
| Field | Type | Description |
|---|---|---|
searchId | string | The search ID returned at submission. |
query | string | The prompt that was fanned out. |
status | string | "completed" or "failed". |
country | string | Search geography the run executed from. |
sources | object[] | One entry per source — see below. |
creditsUsed | number | Total credits charged across all sources. |
synthesis | string | Optional — cross-source synthesis, only present when the AI synthesis pass is enabled for the run (off by default). |
completedAt | string | When the search settled. |
result_url | string | Always present — https://api.anakin.io/v1/ai-visibility/search/{searchId}. Fetch this for the full answers. |
Each sources[] entry:
| Field | Type | Description |
|---|---|---|
source | string | Source slug (chatgpt, gemini, google_ai_overview, …). |
status | string | "completed" or "failed". |
latencyMs | number | Measured engine latency. |
creditsUsed | number | Credits for this source (0 when failed). |
summary | string | Bounded answer preview (≤ 500 chars), completed sources only — not the full answer. |
error | string | Failure reason — failed sources only. |
Example — ai.search.completed
{
"id": "evt_9f2c1d…",
"type": "ai.search.completed",
"createdAt": "2026-07-15T12:03:41Z",
"data": {
"searchId": "3d9db7ff-1c2e-4b8a-9f0d-5e6a7b8c9d0e",
"query": "Best coding agents in 2026",
"status": "completed",
"country": "us",
"creditsUsed": 3,
"sources": [
{ "source": "chatgpt", "status": "completed", "latencyMs": 9100,
"creditsUsed": 1, "summary": "The leading coding agents in 2026 are…" },
{ "source": "gemini", "status": "completed", "latencyMs": 27100,
"creditsUsed": 2, "summary": "Several agents dominate the space…" },
{ "source": "google_ai_overview", "status": "failed", "latencyMs": 150000,
"creditsUsed": 0, "error": "source timed out" }
],
"result_url": "https://api.anakin.io/v1/ai-visibility/search/3d9db7ff-1c2e-4b8a-9f0d-5e6a7b8c9d0e"
}
}Then fetch the complete answers (full text per source):
curl "https://api.anakin.io/v1/ai-visibility/search/3d9db7ff-1c2e-4b8a-9f0d-5e6a7b8c9d0e" \
-H "X-API-Key: your_api_key"The response is documented at GET /v1/ai-visibility/search/{id}.
webhook.test
Sent when you press Test in the dashboard (Account → Webhooks) or call POST /v1/webhooks/{id}/test.
{
"id": "evt_f0e1d2…",
"type": "webhook.test",
"createdAt": "2026-07-13T10:00:00Z",
"data": {
"endpointId": "9b1c22e4-…",
"message": "Test delivery from anakin.io — your endpoint is reachable and this body is signed with your endpoint secret."
}
}