Docs

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:

MethodScopeSigning
Org webhooksAll runs in your orgHMAC-SHA256
Per-run callbackUrlSingle runNone

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:

  1. Click Create webhook
  2. Enter your HTTPS endpoint URL
  3. Select the events to subscribe to (run.completed, run.failed, run.cancelled)
  4. 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

Terminal
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

EventFires when
run.completedAgent finished successfully
run.failedAgent hit a guardrail or encountered an error
run.cancelledRun 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:

HeaderDescriptionExample
webhook-idUnique delivery ID (for idempotency)msg_2Kj9s...
webhook-timestampUnix timestamp of the delivery1737024900
webhook-signatureHMAC-SHA256 signature(s)v1,K5oZfz...
Content-TypeAlways application/jsonapplication/json
User-AgentSender identifiercospec-webhooks/1.0
X-Cospec-EventEvent typerun.completed
X-Cospec-Delivery-IdSame as webhook-idmsg_2Kj9s...
X-Cospec-Delivery-AttemptAttempt 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 withcoSPEC action
2xxDelivery successful — no retry
410 GoneEndpoint decommissioned — stop permanently, disable webhook
Other 4xxClient error — stop retrying
5xx or timeoutServer error — retry with backoff

Best Practices

  • Respond 200 quickly — do heavy processing asynchronously. If your handler takes longer than 10 s, the delivery will time out.
  • Use webhook-id for idempotency — retries resend the same webhook-id. Store processed IDs and skip duplicates.
  • Return 410 for 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:

Terminal
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 webhookscallbackUrl
ScopeAll runsSingle run
Signature verificationHMAC-SHA256None
Retries3 attempts with backoff3 attempts with backoff
RegistrationOne-time setupSet per run
Secret managementRequiredNot needed

On this page