For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Book a demoLog in
DocumentationAPI ReferenceModel VersioningChangelog
DocumentationAPI ReferenceModel VersioningChangelog
    • Studio
    • Support
    • Benchmarks
    • Status
  • Getting Started
    • Overview
    • API Quickstart
    • Dashboard Quickstart
    • Agent Quickstart
  • Dev Tools
    • SDKs
    • CLI
  • Capabilities
      • Configuration
      • Events
      • Best Practices
LogoLogo
Book a demoLog in
On this page
  • How webhooks work
  • Set up a webhook in the dashboard
  • Subscribe to global events
  • Workflow-specific subscriptions
  • Processor run-specific subscriptions
  • Webhook delivery format
  • Configure webhooks with the API
  • Create an endpoint
  • Subscribe to a specific resource
  • Manage endpoints and subscriptions
  • Verifying webhook requests
  • Using the SDK (Recommended)
  • Handling Signed URL Payloads
  • Manual Verification
Webhooks

Configuration

Was this page helpful?
Previous

Events

Next
Built with

Webhooks push events to your server as work completes, so you don’t have to poll for results. You can configure them in the dashboard or with the API — both use the same two building blocks described below.

How webhooks work

Two concepts work together:

  • Endpoint — a destination URL that receives events, along with a signing secret used to verify them. Each endpoint is pinned to a single API version.
  • Subscription — what an endpoint listens for. There are two kinds, and an endpoint can use both at once:
    • Global events are set directly on the endpoint (its enabledEvents). They fire for every resource of that type in the workspace, plus workspace-level lifecycle events like extractor.created and workflow.deployed.
    • Resource-scoped subscriptions bind an endpoint to a single resource — one extractor, classifier, splitter, or workflow — so you only receive that resource’s events.
Global eventsResource-scoped subscriptions
ScopeEvery resource of a type in the workspaceOne specific resource
Configured onThe endpoint (enabledEvents)A subscription attached to the endpoint
Reach for it whenYou want all extract runs, all parse runs, or lifecycle eventsYou only care about a single workflow or extractor

Run events are only delivered for runs created through the API — runs started from the dashboard do not trigger webhooks.

Some events are only available as resource-scoped subscriptions. workflow_run.* events (for example workflow_run.completed) and other per-run workflow events are delivered through workflow subscriptions, not global events. Run-completion events like extract_run.processed are available at both levels — globally for all extractors, or scoped to a single extractor.

Set up a webhook in the dashboard

To set up a webhook, create one in the “Developers” tab on the sidebar, under the “Webhook endpoints” section.

Subscribe to global events

When creating a webhook endpoint, you can subscribe to the GLOBAL event types. These events are not associated with a specific workflow or processor and are global to your workspace.

See the Events section for more details on the different event types you can subscribe to.

Workflow-specific subscriptions

After you have created one or more webhook endpoints from the “Developer” tab, you can subscribe to events for specific workflows by navigating to the desired workflow(s) and opening the “Webhook Subscriptions” menu.

Then you can create new subscriptions for the desired endpoints and event types.

Once subscribed, you will start to receive events to the specified webhook endpoint based on the selected event types each time you run a file(s) through that workflow.

See the Events section for more details on the different event types you can subscribe to and the shape of the payload for each event type.

Processor run-specific subscriptions

You can also subscribe to events for specific processor runs. This is necessary if you are running processors directly and not via a workflow.

To start, navigate to the “Overview” tab of the given processor in Studio, click “Webhook Subscriptions”, and then click the “Add webhook subscription” button.

Next, you can select the desired event types you would like to subscribe to and assign the webhook endpoint you would like to receive events at.

Webhook delivery format

We support two delivery formats for webhooks:

  • JSON: This is the default and most common format. It is a simple JSON object with the event data in the body.
  • Signed Download URL: This is a signed URL that allows you to download the event data as a file. This is useful for scenarios where the webhook event payload is too large to fit in the body of the request, or you have payload size constraints on your webhook endpoint.

We also provide the option to send all payloads as a signed download URL over a certain size threshold, which can be configured in bytes when creating/updating a webhook endpoint.

The signed download URL has a one hour expiration time. When manually re-trying a webhook request, a new URL will be generated each time, and when viewing the webhook history for events sent as a URL, a new URL is generated for viewing.

To configure the delivery format, you can select the desired format in the “Advanced options” dropdown when creating/updating a webhook endpoint.

Configure webhooks with the API

Everything you can do in the dashboard you can also do programmatically. This is the path to reach for when you provision webhooks per customer or environment, manage them in infrastructure-as-code, or let an agent wire up its own event delivery.

Create an endpoint

Creating an endpoint registers a URL and sets its global enabledEvents. The response includes a signingSecret — it is returned only once, so store it securely. You’ll need it to verify deliveries.

$curl -X POST https://api.extend.ai/webhook_endpoints \
> -H "Authorization: Bearer YOUR_API_KEY" \
> -H "x-extend-api-version: 2026-02-09" \
> -H "Content-Type: application/json" \
> -d '{
> "url": "https://example.com/webhooks",
> "name": "Production webhook",
> "enabledEvents": ["extract_run.processed", "extract_run.failed"],
> "apiVersion": "2026-02-09"
> }'

Pass an empty enabledEvents array to create an endpoint with no global events — useful when you only plan to route specific resources to it via subscriptions.

Subscribe to a specific resource

To receive events for a single resource, create a subscription against an existing endpoint. Set the resourceType (extractor, classifier, splitter, or workflow), the resourceId, and the enabledEvents that are valid for that resource type. If a subscription already exists for the same endpoint and resource, it is updated rather than duplicated.

$curl -X POST https://api.extend.ai/webhook_subscriptions \
> -H "Authorization: Bearer YOUR_API_KEY" \
> -H "x-extend-api-version: 2026-02-09" \
> -H "Content-Type: application/json" \
> -d '{
> "webhookEndpointId": "wh_Xj8mK2pL9nR4vT7qY5wZ",
> "resourceType": "workflow",
> "resourceId": "workflow_id_here",
> "enabledEvents": ["workflow_run.completed", "workflow_run.failed"]
> }'

The valid enabledEvents for a subscription depend on its resourceType:

Resource typeAvailable events
extractorextract_run.processed, extract_run.failed, batch_processor_run.processed, batch_processor_run.failed
classifierclassify_run.processed, classify_run.failed, batch_processor_run.processed, batch_processor_run.failed
splittersplit_run.processed, split_run.failed, batch_processor_run.processed, batch_processor_run.failed
workflowworkflow_run.completed, workflow_run.failed, workflow_run.needs_review, workflow_run.rejected, workflow_run.cancelled, workflow_run.step_run.processed

Manage endpoints and subscriptions

Both resources support the full set of list, retrieve, update, and delete operations. A few things to know:

  • Updates are partial — only the fields you send are changed. An endpoint’s apiVersion cannot be changed after creation.
  • Deleting an endpoint also deletes all of its subscriptions, and is permanent.
  • Filtering — list subscriptions by webhookEndpointId to see everything an endpoint listens for, or by resourceId to see every endpoint a resource notifies.

See the API reference for the full schemas and every operation: Create Webhook Endpoint and Create Webhook Subscription.

Prefer the command line? The Extend CLI wraps these endpoints with extend webhooks endpoints, extend webhooks subscriptions, and extend webhooks verify.

Verifying webhook requests

Extend will sign each webhook request using a secret unique to the webhook. You can use this signature along with the timestamp to verify that the request is coming from Extend as well as protect against replay attacks.

Using the SDK (Recommended)

The easiest way to verify webhook requests is using the SDK’s built-in helper methods. These handle signature verification, timestamp validation, and event parsing automatically.

1from extend_ai import Extend
2from extend_ai.wrapper.errors import WebhookSignatureVerificationError
3
4client = Extend(token="YOUR_API_KEY")
5
6@app.post("/webhook")
7def handle_webhook(request):
8 try:
9 # verify_and_parse validates the signature and parses the event in one step
10 event = client.webhooks.verify_and_parse(
11 body=request.body.decode(), # Raw request body as string
12 headers=dict(request.headers), # Request headers as dict
13 signing_secret="wss_your_signing_secret" # Your webhook signing secret
14 )
15
16 # Handle the event based on type
17 if event["eventType"] == "workflow_run.completed":
18 print("Workflow completed:", event["payload"])
19 elif event["eventType"] == "extract_run.processed":
20 print("Extract run processed:", event["payload"])
21 # ... handle other event types
22
23 return {"status": "ok"}
24 except WebhookSignatureVerificationError:
25 return {"error": "Invalid signature"}, 401

The Go SDK does not include a webhook verification helper. Go users should verify the signature manually — see Manual Verification below.

The signing secret can be found in the webhooks table under the “Developer” tab:

Handling Signed URL Payloads

For large payloads, webhooks may be delivered via a signed URL instead of inline. To handle these:

1event = client.webhooks.verify_and_parse(body, headers, secret, allow_signed_url=True)
2
3if client.webhooks.is_signed_url_event(event):
4 # Fetch the full payload from the signed URL
5 full_event = await client.webhooks.fetch_signed_payload(event)
6 print("Full payload:", full_event["payload"])
7else:
8 # Normal inline payload
9 print("Payload:", event["payload"])

Manual Verification

If you’re not using the SDK, you can verify webhook signatures manually:

  1. Retrieve the timestamp of the request from x-extend-request-timestamp, the body of the request, and the signing secret associated with the webhook.
  2. Concatenate the timestamp and request body using the following format: v0:${timestamp}:${requestBody}
  3. Compute a HMAC 256 digest on the resulting string using the signing secret as the key.
  4. Compare this digest with the signature provided in x-extend-request-signature. If they are equal, then the request is verified to be from Extend.
1import hmac
2import hashlib
3import time
4
5timestamp = request.headers.get('x-extend-request-timestamp')
6received_signature = request.headers.get('x-extend-request-signature')
7signing_secret = "wss_your_signing_secret"
8
9# Validate timestamp to prevent replay attacks
10current_time = int(time.time())
11if current_time - int(timestamp) > 300: # 5 minutes
12 raise ValueError('Request timestamp too old')
13
14request_body_string = request.body.decode()
15
16message = f"v0:{timestamp}:{request_body_string}"
17expected_signature = hmac.new(
18 signing_secret.encode('utf-8'),
19 message.encode('utf-8'),
20 hashlib.sha256
21).hexdigest()
22
23if not hmac.compare_digest(expected_signature, received_signature):
24 raise ValueError('Invalid webhook signature')
25
26# Proceed with webhook processing