Guides
Webhooks
Get signed events when runs start and end, when an agent asks for input, and what each model call cost
View as Markdown · Docs index for coding agents
camelRun can tell your server when runs start and end, when an agent asks a person for input, and what each model response cost, so you can react without holding a connection open. Register an endpoint with the event types it wants:
curl https://run.camelai.com/v1/webhooks \
-H "Authorization: Bearer $CAMELAI_API_KEY" -H "Content-Type: application/json" \
-d '{"url": "https://example.com/hooks/agents", "events": ["run.completed", "run.failed", "input.requested"], "description": "prod"}'The answer carries the endpoint's id (we_...) and its signing secret
(whsec_...), shown only this once.
| Endpoint | |
|---|---|
GET /v1/webhooks | List your endpoints (at most 16) |
GET, PATCH, DELETE /v1/webhooks/{webhookId} | Read, change or remove one |
POST /v1/webhooks/{webhookId}/secret | Replace its secret. The old one also signs for 24 hours |
Events
Every event is an envelope:
{"id": "evt_3f9c0a1b2c3d4e5f60718293", "type": "run.completed", "created": 1790000000,
"data": {"agentId": "client_...", "requestId": "...", "method": "prompt", "metadata": {"source": "web"},
"replyIndex": 3, "messageCount": 4,
"usage": {"responses": 2, "input": 2300, "output": 140, "cacheRead": 0, "cacheWrite": 0, "costUsd": 0.0081}}}| Type | When | data |
|---|---|---|
run.started | A run began | agentId, requestId, method, actor, metadata |
run.completed | It ended without an error | As above, plus usage, and stopped (input_required with inputIds, or spend_limit) if it stopped early |
run.failed | It ended with an error | As above, plus usage, error, and uncertain when a restart cut it short |
input.requested | The agent asks a person for input | agentId, requestId, inputId, toolCallId, kind, expiresAt |
input.resolved | An input settled | agentId, requestId, inputId, state |
usage.recorded | A model response's usage was counted | agentId, requestId, subject, actor, context, keyScope, provider, model, tokens, cost: {usd, source} |
Payloads are small: ids and the key facts. Read the rest with your API key, for
example the run's reply from GET /v1/agents/{id}/requests/{requestId} at
outcome.result.reply. metadata is what the message that started the run
carried, so you can route an event without a lookup.
Verify and handle
Requests are signed per Standard Webhooks
with webhook-id, webhook-timestamp and webhook-signature headers. Any
Standard Webhooks library verifies them:
import { Webhook } from "standardwebhooks";
import type { WebhookEvent } from "@camelai/run";
const event = new Webhook(process.env.WEBHOOK_SECRET!.replace(/^whsec_/, "")).verify(body, headers) as WebhookEvent;- Delivery is at least once. Answer 2xx within 10 seconds; anything else is retried with exponential backoff, from 5 seconds to an hour apart, for 3 days.
- Dedupe by
id(a retried event keeps it), and order bycreated, because retries can reorder deliveries. - Do the work after answering: queue it, then return 200.
Continue from a webhook
A handler can act on the agent itself. Send your own ids as metadata with
each message, and they come back on the run's events:
// When the message was sent: agent.run(text, { metadata: { thread: thread.id } })
if (event.type === "run.failed" && event.data.metadata?.thread) {
const agent = await agents.upsert(`thread-${event.data.metadata.thread}`, config);
await agent.run("The last step failed; tell the user what happened and what to try.");
}A serverless handler that upserts without local tools never contends with the process serving the agent's tools.