Outbound Webhooks
ColabHive signs and delivers an HTTPS notification when an inference task, Action invocation, training run, or training merge reaches a terminal state — so you stop polling for work that may take minutes or hours.
An event tells you when something finished and which resource changed. It does not carry the
result: payloads contain no prompts, inputs, outputs, logs, or artifacts. Use data.resource_url
with your normal API credentials to fetch the actual state. Polling remains fully supported and is
not deprecated.
Quickstart
1. Register your destination. You get the signing secret exactly once — store it before the response leaves your screen.
curl -X POST https://api.colabhive.com/api/builder/v1/webhook-endpoints \
-H "X-API-Key: $KEY" -H "X-Account-ID: $ACCT" \
-H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
-d '{
"name": "production-events",
"url": "https://events.example.com/colabhive",
"event_types": ["inference.task.succeeded", "training.run.completed"]
}'
2. Answer the verification challenge. The destination starts as pending_verification and
ColabHive immediately sends a signed webhook.endpoint_verification event. Echo the challenge back
and the endpoint flips to active.
3. Verify every delivery before you trust it. Check the signature over the raw bytes, then
deduplicate on event.id — delivery is at-least-once, so the same event can legitimately arrive
twice.
from colabhive.webhooks import WebhookEvent, verify_signature # pip install colabhive==0.7.0
raw_body = await request.body() # exact bytes, before any parsing
verify_signature(raw_body, request.headers, signing_secret)
event = WebhookEvent.from_raw_body(raw_body)
if already_processed(event.id): # at-least-once: duplicates are normal
return 200
record(event.id) # persist BEFORE side effects
return 200 # 2xx only once your work is durable
Three mistakes account for most broken receivers, and all three are silent:
- parsing before verifying. Re-serialized JSON has a different signature even when it is semantically identical, so verify the exact bytes you received;
- returning 2xx before the work is committed. A 2xx is a promise; ColabHive will not resend;
- assuming exactly-once. Deduplicate on
event.id, not on attempt number.
Management lifecycle
The Builder REST API under /api/builder/v1 is the authoritative management contract. Reads require
the viewer role; test delivery and manual redelivery require operator; create, update, delete,
and secret rotation require admin.
Generic Actions do not duplicate these administrative operations: their OpenAPI metadata keeps
Actions exposure at none.
| Method | Path | Purpose |
|---|---|---|
POST | /webhook-endpoints | Create a destination and reveal its whsec_… signing secret once. |
GET | /webhook-endpoints | List redacted destinations using cursor pagination. |
GET | /webhook-endpoints/{endpoint_id} | Read redacted configuration and its ETag. |
PATCH | /webhook-endpoints/{endpoint_id} | Change name, destination, subscriptions, or enabled state; requires If-Match. |
DELETE | /webhook-endpoints/{endpoint_id} | Disable and delete a destination; requires If-Match. |
POST | /webhook-endpoints/{endpoint_id}/rotate-secret | Rotate gracefully or in emergency mode. |
POST | /webhook-endpoints/{endpoint_id}/test | Queue a signed webhook.test event. |
GET | /webhook-deliveries | List delivery history, newest first, with cursor pagination. |
GET | /webhook-deliveries/{delivery_id} | Read one delivery with its append-only attempt history. |
POST | /webhook-deliveries/{delivery_id}/redeliver | Re-send an existing event under a new generation. |
Create, rotate, and test requests support Idempotency-Key. Store returned secrets immediately:
normal GET/list responses expose only secret_hint, url_origin, and the fixed redaction
https://***. Exact idempotent replay is bounded and does not turn GET into a secret-recovery API.
Destination verification
A new or changed URL starts as pending_verification. ColabHive sends a signed
webhook.endpoint_verification event containing data.verification_challenge. Within the
verification window, return HTTP 2xx with both:
ColabHive-Verification-Challenge: <exact challenge>; and- the exact compact JSON body
{"verification_challenge":"<exact challenge>"}.
Only a verified active endpoint receives subscribed or test events. Redirects do not count as
verification. Changing the URL invalidates the old challenge and starts verification again.
Event matrix
| Family | Event types | Resource URL | Origins |
|---|---|---|---|
| Inference | inference.task.succeeded, inference.task.failed | /api/builder/v1/tasks/{id} | REST, OpenAI-compatible, MCP |
| Invocation | invocation.completed, invocation.failed | /api/builder/v1/invocations/{id} | Actions REST, MCP |
| Training run | training.run.completed, training.run.failed, training.run.cancelled | /api/builder/v1/training/runs/{id} | Training API |
| Training merge | training.merge.completed, training.merge.failed, training.merge.cancelled | /api/builder/v1/training/runs/{id} | Merge API |
webhook.endpoint_verification and webhook.test are lifecycle events and cannot be selected as
normal subscriptions. Intermediate progress, logs, metrics, queue changes, and retries do not emit
public events. A failure event represents final failure after recovery/retry policy is exhausted.
Every payload uses envelope version 1:
{
"id": "event-uuid",
"object": "event",
"api_version": "1",
"type": "training.run.completed",
"created_at": "2026-08-17T12:00:00Z",
"account_id": "account-uuid",
"data": {
"resource": {"id": "run-uuid", "object": "training_run", "status": "completed"},
"resource_url": "/api/builder/v1/training/runs/run-uuid"
}
}
The canonical event names and JSON Schemas live in contracts/webhooks/v1. Consumers should ignore
transport attempt count when deduplicating and use the envelope id / ColabHive-Event-Id.
Verify signatures before parsing
Deliveries include:
| Header | Meaning |
|---|---|
ColabHive-Signature | t=<unix>,v1=<hex HMAC-SHA256>; rotation can include two v1 values. |
ColabHive-Event-Id | Stable event/deduplication ID across delivery attempts. |
ColabHive-Delivery-Id | Destination-specific delivery ID. |
ColabHive-Attempt | One-based transport attempt number. |
ColabHive-Webhook-Version | 1. |
Compute HMAC-SHA256 over the exact bytes str(timestamp) + "." + raw_request_body, using the full
whsec_… value exactly as returned. Compare digests in constant time and reject timestamps outside your replay
window. Do not parse, normalize, pretty-print, or reserialize JSON first—even equivalent JSON has a
different signature. Persist event.id atomically before performing side effects.
Python SDK 0.7.0 contains verify_signature, header helpers, event models, and
client.webhooks with client.webhooks.deliveries. It is published on PyPI:
pip install colabhive==0.7.0
from colabhive.webhooks import WebhookEvent, verify_signature
raw_body = await request.body()
verification = verify_signature(raw_body, request.headers, signing_secret)
event = WebhookEvent.from_raw_body(raw_body)
assert verification.event_id == event.id
Complete receiver examples ship in the documentation bundle under snippets/python and
snippets/node.
SDK and MCP surfaces
The Python SDK 0.7.0 mirrors all seven management operations and supplies receiver-side verification
helpers. MCP 0.3.0 (pip install colabhive-mcp==0.3.0) mirrors list, get, create, update, delete,
rotate, and test while preserving the REST roles, ETag/If-Match, and Idempotency-Key
contract. All seven MCP tools are hidden by
default and require COLABHIVE_MCP_WEBHOOK_TOOLS_ENABLED=true. Create and rotate return a one-time
signing secret and require the additional COLABHIVE_ALLOW_WEBHOOK_SECRET_TOOLS=true gate; do not
expose their results to untrusted model context. The MCP surface stays opt-in even though the REST
management routes are live.
Delivery semantics and retries
Delivery is at least once. Receivers must tolerate duplicates and return 2xx only after their durable work is committed. ColabHive retries timeouts, connection failures, HTTP 408/425/429, and 5xx. Other 4xx responses are permanent; HTTP 410 disables the endpoint; redirects are rejected.
The retry policy permits up to eight attempts within a 48-hour deadline, approximately
at 0s, 30s, 2m, 10m, 1h, 6h, 24h, and 36h plus bounded final jitter. A valid Retry-After on 429 or
503 can delay the next attempt but never extend the deadline. Exhausted deliveries are retained in a
dead_lettered state.
Delivery history and manual redelivery
GET /webhook-deliveries returns deliveries newest first and accepts endpoint_id, status, and
event_type filters. The list omits attempts; GET /webhook-deliveries/{delivery_id} adds the
append-only attempt history. Neither route returns the destination URL, the signing secret, the
event payload, or any remote response body — an attempt exposes only its outcome, a sanitized error
code, an HTTP status class, and timing.
POST /webhook-deliveries/{delivery_id}/redeliver re-sends an existing event. It keeps the same
event ID and the same frozen payload, creates the next delivery_generation, and references the
previous one through redelivery_of_delivery_id. It pins the destination's currently active and
verified version and secrets, so it never resurrects a retired URL or key.
The operation is idempotent by structure rather than by header: while a redelivery of the same
delivery is still non-terminal, repeating the request returns that one with replayed: true instead
of creating another generation. It answers 409 when the destination is not active and verified,
and 410 once the event is past its retention window.
A redelivery never reopens, retries, or modifies the inference, invocation, training, or merge workload that produced the event. A dead-lettered delivery is terminal for that delivery alone; the run that generated it stays exactly as it was.
curl -X POST \
"https://api.colabhive.com/api/builder/v1/webhook-deliveries/$DELIVERY_ID/redeliver" \
-H "X-API-Key: $KEY" -H "X-Account-ID: $ACCT"
Retention
| What | Kept for |
|---|---|
| Events and deliveries | 30 days |
| Per-attempt history | 14 days |
The attempt history expires before the delivery that groups it: it exists to operate a recent incident, not as an archive. A delivery you can still see may therefore show no attempts.
Once an event passes its window it is collected, and POST /webhook-deliveries/{delivery_id}/redeliver answers 410 — the payload no
longer exists to re-send. Export anything you need to keep beyond that.
These windows are not configurable per account today.
Security and destination restrictions
- Destinations must use HTTPS on port 443, without URL userinfo or fragments.
- Literal IPs, localhost, non-global/private/link-local/metadata addresses, ColabHive-owned blocked domains, unsafe DNS results, redirects, and DNS rebinding are rejected.
- Delivery resolves, validates, pins, connects, and revalidates the peer address. TLS verification remains enabled.
- Destination URLs and signing secrets are encrypted at rest; APIs, logs, metrics, and audit records return only redacted projections.
- Keep current and previous secrets only for the documented graceful-rotation overlap; emergency rotation invalidates the prior secret immediately.
Current limits
There is no public API yet for replay of an arbitrary historical event that never produced a delivery, retention configuration, or per-account delivery quotas. Redelivery operates on an existing delivery record, not on any past event you choose. The management API also does not accept a callback URL per inference/training request.
Polling GET /tasks/{id}, GET /invocations/{id} and GET /training/runs/{id} stays available and
is the right fallback whenever a receiver is down or you would rather not run one.