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": { }
}
FieldTypeDescription
idstringUnique event ID (evt_…).
typestringThe event type — same as the X-Anakin-Event header.
createdAtstringWhen the event was created (ISO 8601).
dataobjectEvent-specific fields, documented per event below.

monitor.change keeps its original flat payload (no envelope) for backward compatibility — see below.


Event taxonomy

EventFires when
job.completedAn async job finishes successfully — data.jobType tells you which product
job.failedAn async job fails
batch.completedA batch URL-scrape settles successfully (fires once per batch)
batch.failedA batch URL-scrape fails as a whole
wire.job.completedA Wire task finishes successfully
wire.job.failedA Wire task fails
monitor.changeA Website Monitor detects a change
ai.search.completedAn AI Visibility search settles with at least one source answered
ai.search.failedAn AI Visibility search settles with every source failed
webhook.testYou 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

FieldTypeDescription
jobIdstringThe job ID returned at submission.
jobTypestringurl_scraper, web_scraper, map, crawl, agentic_search, or search.
statusstring"completed" or "failed".
urlstringThe submitted URL.
countrystringProxy country — present when set on the job.
cachedbooleanPresent when the result was served from cache.
creditsUsednumberCredits charged — present when known.
durationMsnumberProcessing time in milliseconds — present when known.
createdAtstringWhen the job was created.
completedAtstringWhen the job settled.
result_urlstringAlways present. The canonical authenticated GET endpoint for the full result — fetch it with your API key.
resultobjectThe full canonical result document, inlined only when the whole envelope is ≤ 256 KB.
resultOmittedstring"size" or "unavailable" — present when result was dropped.
errorstringFailure 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:

Jobresult_url
url_scraper, batch_url_scraperhttps://api.anakin.io/v1/url-scraper/{jobId}
web_scraperhttps://api.anakin.io/v1/web-scraper/{jobId}
maphttps://api.anakin.io/v1/map/{jobId}
crawlhttps://api.anakin.io/v1/crawl/{jobId}
agentic_searchhttps://api.anakin.io/v1/agentic-search/{jobId}
searchhttps://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 resultresult_url is a normal authenticated endpoint: GET it 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_url works for every event and every result size; a receiver that only reads inline result silently 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 tooPOST /v1/search and POST /v1/url-scraper/scrape return results inline in the HTTP response, but registered endpoints subscribed to job.completed / job.failed still 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

FieldTypeDescription
jobIdstringThe batch job ID returned at submission.
jobTypestringAlways batch_url_scraper.
statusstring"completed" or "failed".
urlsTotalnumberURLs submitted in the batch.
completednumberURLs that scraped successfully.
failednumberURLs that failed.
creditsUsednumberCredits charged for the batch.
result_urlstringGET 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

FieldTypeDescription
jobIdstringThe Wire job ID.
actionIdstringThe action that ran (e.g. li_profile_scrape).
actionNamestringHuman-readable action name.
catalogSlugstringThe catalog the action belongs to.
statusstring"completed" or "failed".
creditsUsednumberCredits charged.
durationMsnumberExecution time in milliseconds.
errorstringFailure reason — wire.job.failed only.
result_urlstringAlways presenthttps://api.anakin.io/v1/wire/jobs/{jobId}.
resultobjectThe 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 payloadtype 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.* and wire.job.* events, AI Visibility deliveries don't carry a result object at all: each source's answer can run to hundreds of KB, so the event inlines only a bounded per-source summary (≤ 500 chars). To get the complete answers, GET the result_url with your API key — it is always present.

data fields

FieldTypeDescription
searchIdstringThe search ID returned at submission.
querystringThe prompt that was fanned out.
statusstring"completed" or "failed".
countrystringSearch geography the run executed from.
sourcesobject[]One entry per source — see below.
creditsUsednumberTotal credits charged across all sources.
synthesisstringOptional — cross-source synthesis, only present when the AI synthesis pass is enabled for the run (off by default).
completedAtstringWhen the search settled.
result_urlstringAlways presenthttps://api.anakin.io/v1/ai-visibility/search/{searchId}. Fetch this for the full answers.

Each sources[] entry:

FieldTypeDescription
sourcestringSource slug (chatgpt, gemini, google_ai_overview, …).
statusstring"completed" or "failed".
latencyMsnumberMeasured engine latency.
creditsUsednumberCredits for this source (0 when failed).
summarystringBounded answer preview (≤ 500 chars), completed sources only — not the full answer.
errorstringFailure 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."
  }
}