camelAI Documentation

Guides

Webhooks

Get signed events when runs start and end, when an agent asks for input, and what each model call cost

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:

bash
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/webhooksList your endpoints (at most 16)
GET, PATCH, DELETE /v1/webhooks/{webhookId}Read, change or remove one
POST /v1/webhooks/{webhookId}/secretReplace its secret. The old one also signs for 24 hours

Events

Every event is an envelope:

json
{"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}}}
TypeWhendata
run.startedA run beganagentId, requestId, method, actor, metadata
run.completedIt ended without an errorAs above, plus usage, and stopped (input_required with inputIds, or spend_limit) if it stopped early
run.failedIt ended with an errorAs above, plus usage, error, and uncertain when a restart cut it short
input.requestedThe agent asks a person for inputagentId, requestId, inputId, toolCallId, kind, expiresAt
input.resolvedAn input settledagentId, requestId, inputId, state
usage.recordedA model response's usage was countedagentId, 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:

ts
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 by created, 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:

ts
// 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.