Webhooks
Receive real-time notifications when runs complete, fail, or are cancelled
Overview
Instead of polling GET /v1/runs/:id, you can have coSPEC push notifications to your server when runs reach a terminal state.
There are two mechanisms:
| Method | Scope | Signing |
|---|---|---|
| Org webhooks | All runs in your org | HMAC-SHA256 |
Per-run callbackUrl | Single run | None |
This guide covers both, starting with org webhooks.
Managing Webhooks in the Dashboard
Webhooks can be created and deleted from the Webhooks page in the dashboard. The page also shows delivery health per endpoint so you can monitor reliability at a glance.
Creating a webhook:
- Click Create webhook
- Enter your HTTPS endpoint URL
- Select the events to subscribe to (
run.completed,run.failed,run.cancelled) - Copy the signing secret shown in the next step — it is displayed only once
Deleting a webhook removes it immediately. In-flight deliveries for running runs will still complete.
Registering a Webhook
curl -X POST https://api.cospec.io/v1/webhooks \
-H "Authorization: Bearer csk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/cospec",
"events": ["run.completed", "run.failed", "run.cancelled"]
}'Response:
{
"id": "wh_abc123",
"url": "https://your-server.com/webhooks/cospec",
"events": ["run.completed", "run.failed", "run.cancelled"],
"secret": "whsec_dGhpcyBpcyBhIHNhbXBsZSBzZWNyZXQ=",
"createdAt": "2026-01-15T10:00:00Z"
}The secret is shown only once in the creation response. Store it immediately — you'll need it to verify signatures.
Constraints:
- URL must use HTTPS
- Maximum 25 webhooks per organization
- Each (org, url) pair must be unique
Attempting to exceed the limit returns WEBHOOK_LIMIT_EXCEEDED. Registering a duplicate URL returns WEBHOOK_URL_DUPLICATE.
Events
| Event | Fires when |
|---|---|
run.completed | Agent finished successfully |
run.failed | Agent hit a guardrail or encountered an error |
run.cancelled | Run was cancelled via the API |
Payload
Every webhook delivery sends a JSON body with the event type, timestamp, and the full run object:
{
"event": "run.completed",
"timestamp": "2026-01-15T10:35:00Z",
"run": {
"id": "run_abc123",
"status": "completed",
"prompt": "Refactor the auth middleware to use JWT",
"repo": "your-org/your-repo",
"outputs": [
{
"type": "pr",
"url": "https://github.com/your-org/your-repo/pull/42",
"title": "Refactor auth middleware to use JWT",
"number": 42
}
],
"usage": { "totalCostUsd": 0.42 },
"createdAt": "2026-01-15T10:30:00Z",
"completedAt": "2026-01-15T10:35:00Z"
}
}The run object matches the response from GET /v1/runs/:id.
Delivery Headers
Each webhook request includes the following headers:
| Header | Description | Example |
|---|---|---|
webhook-id | Unique delivery ID (for idempotency) | msg_2Kj9s... |
webhook-timestamp | Unix timestamp of the delivery | 1737024900 |
webhook-signature | HMAC-SHA256 signature(s) | v1,K5oZfz... |
Content-Type | Always application/json | application/json |
User-Agent | Sender identifier | cospec-webhooks/1.0 |
X-Cospec-Event | Event type | run.completed |
X-Cospec-Delivery-Id | Same as webhook-id | msg_2Kj9s... |
X-Cospec-Delivery-Attempt | Attempt number (1-based) | 1 |
Retry Policy
coSPEC uses at-least-once delivery with automatic retries.
- Attempts: 3 (initial + 2 retries)
- Backoff: Exponential — 2 s base, 30 s max
- Timeout: 10 s per attempt
| Your server responds with | coSPEC action |
|---|---|
2xx | Delivery successful — no retry |
410 Gone | Endpoint decommissioned — stop permanently, disable webhook |
Other 4xx | Client error — stop retrying |
5xx or timeout | Server error — retry with backoff |
Best Practices
- Respond
200quickly — do heavy processing asynchronously. If your handler takes longer than 10 s, the delivery will time out. - Use
webhook-idfor idempotency — retries resend the samewebhook-id. Store processed IDs and skip duplicates. - Return
410for decommissioned endpoints — this tells coSPEC to stop sending and disable the webhook, keeping your account clean.
Per-Run Callbacks (callbackUrl)
For simpler use cases, you can set a callbackUrl on individual runs instead of registering an org-wide webhook:
curl -X POST https://api.cospec.io/v1/runs \
-H "Authorization: Bearer csk_live_..." \
-H "Content-Type: application/json" \
-d '{
"repo": "your-org/your-repo",
"prompt": "Add input validation to the signup form",
"template": "your-template",
"model": "sonnet",
"callbackUrl": "https://your-server.com/callback"
}'When the run finishes, coSPEC sends the same payload to your callback URL. The run object includes a callbackStatus field (pending, delivered, or failed) so you can check delivery status.
Key differences from org webhooks:
| Org webhooks | callbackUrl | |
|---|---|---|
| Scope | All runs | Single run |
| Signature verification | HMAC-SHA256 | None |
| Retries | 3 attempts with backoff | 3 attempts with backoff |
| Registration | One-time setup | Set per run |
| Secret management | Required | Not needed |