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.
Webhooks are currently API-only — there is no UI for managing them in the dashboard yet.
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 |