Receive real-time notifications when pipeline executions and agent runs complete
Webhooks
Webhooks let your application receive push notifications instead of polling for execution status. When you start a pipeline run or agent run, supply a webhook_url in the request body. The platform queues a delivery after the execution completes.
Registering a webhook URL
Pass webhook_url in the run request:
# Pipeline run
curl -X POST https://api.9thsense.ai/v1/pipeline/run \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"context_id": "passport_extraction",
"tenant_id": "acme-corp",
"input": {"filename": "passport.jpg", "data": "<base64>"},
"sync": false,
"webhook_url": "https://your-app.example.com/webhooks/9thsense"
}'
# Agent run
curl -X POST https://api.9thsense.ai/v1/agent/run \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"goal": "thailand_visa",
"tenant_id": "acme-corp",
"documents": [{"filename": "passport.jpg", "data": "<base64>", "mime_type": "image/jpeg"}],
"sync": false,
"webhook_url": "https://your-app.example.com/webhooks/9thsense"
}'
webhook_url is only used when sync is false. Synchronous runs return the result directly in the HTTP response.
Delivery
The webhook worker polls the webhook_outbox table every 15 seconds. When it finds pending entries it delivers up to 10 at a time by POSTing the payload as JSON:
POST https://your-app.example.com/webhooks/9thsense
Content-Type: application/json
User-Agent: 9thSense-Webhooks/1.0
Your endpoint must return a 2xx status code within 30 seconds. Any other response (non-2xx, timeout, or connection error) is treated as a failure.
Payload structure
The webhook body is the execution result — the same JSON you would receive from a synchronous run or from polling GET /v1/pipeline/executions/{execution_id}:
{
"execution_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"context_id": "passport_extraction",
"status": "completed",
"tenant_id": "acme-corp",
"result": {
"full_name": "Rahul Sharma",
"date_of_birth": "1990-03-15",
"passport_number": "K1234567",
"nationality": "INDIA",
"date_of_expiry": "2030-03-14"
},
"steps": [
{"tool": "extract_fields", "order": 0, "status": "ok", "duration_ms": 1240}
],
"total_ms": 1350,
"started_at": "2026-03-25T09:00:00Z",
"completed_at": "2026-03-25T09:00:01.350Z"
}
Retry policy
The platform retries failed deliveries automatically:
| Attempt | Behaviour |
|---|---|
| 1–4 | Status set back to pending; retried on the next worker poll cycle |
| 5 | Status set to failed; no further attempts |
The worker polls every 15 seconds (webhook_worker_interval = 15). The maximum number of attempts before a webhook is permanently failed is 5 (webhook_max_attempts = 5).
Entries in the webhook_outbox table cycle through statuses: pending → delivered or pending → (after 5 attempts) failed.
Receiving webhooks
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/webhooks/9thsense")
async def receive_webhook(request: Request):
payload = await request.json()
execution_id = payload.get("execution_id")
status = payload.get("status")
result = payload.get("result", {})
if status == "completed":
# Process extraction results
print(f"Execution {execution_id} completed: {result}")
elif status == "failed":
print(f"Execution {execution_id} failed")
# Must return 2xx or the platform will retry
return {"received": True}
Keep your endpoint fast. The delivery client waits up to 30 seconds. If your handler needs to do slow work (database writes, downstream calls), acknowledge immediately and process asynchronously:
import asyncio
@app.post("/webhooks/9thsense")
async def receive_webhook(request: Request, background_tasks):
payload = await request.json()
background_tasks.add_task(process_result, payload)
return {"received": True} # return immediately
Webhook delivery stats
Query delivery statistics for the last N days:
curl "https://api.9thsense.ai/v1/analytics/webhooks?days=7" \
-H "X-Api-Key: $API_KEY"
Response:
{
"delivered": 482,
"failed": 3,
"pending": 1,
"total": 486
}
Requires the read scope.
Debugging failed webhooks
If deliveries are failing, check:
- Reachability — your endpoint must be publicly accessible.
localhostURLs will never succeed. - Response code — ensure your handler returns a 2xx status. Redirects (3xx) are not followed.
- Timeout — the delivery client has a 30-second timeout. Acknowledge before doing slow processing.
- Stats — use
GET /v1/analytics/webhooksto check whether the platform is attempting delivery.