Agent skill
camelAI Webhooks Skill
Receive and verify camelAI (camelRun) webhooks, signed per Standard Webhooks. Use when setting up a camelAI webhook endpoint, getting the per-endpoint whsec_ signing secret, debugging webhook-id / webhook-timestamp / webhook-signature verification, or handling events like run.completed, run.failed, input.requested, usage.recorded, voice.session.ended and billing.balance.low.
Install this skill
npx skills add hookdeck/webhook-skills --skill camel-ai-webhooks
camelAI's hosted agent runtime, camelRun, sends webhooks for runs, human input requests, usage, voice sessions, billing and end-user credits. Every delivery is signed per Standard Webhooks with webhook-id, webhook-timestamp and webhook-signature headers. This is camelAI (camelai.com), not CAMEL-AI.org, the open-source multi-agent framework.
When to Use This Skill
- How do I receive camelAI webhooks?
- How do I verify a camelAI webhook signature?
- Where do I find my camelAI webhook
whsec_signing secret? - How do I handle
run.completed,run.failedorinput.requestedevents? - Why is my camelAI
webhook-signatureverification failing? - Why do Hookdeck retries of camelAI webhooks fail verification?
Verification (core)
| Header | Value |
|---|---|
webhook-id | The event id (evt_...), the same on every retry |
webhook-timestamp | Unix seconds of this delivery attempt |
webhook-signature | One or more space-separated v1,<base64 HMAC-SHA256> entries |
Signed content is {webhook-id}.{webhook-timestamp}.{raw body}. The key is the base64-decoded part of the endpoint's secret after whsec_. Each endpoint has its own secret. camelAI has no webhook SDK, so the examples use standardwebhooks (npm, PyPI), which compares in constant time against every v1, entry. Read webhook-* headers and, when webhook-id is absent, fall back to the svix-* headers, as Hookdeck does.
const { Webhook } = require('standardwebhooks');
// rawBody: exact request body string, NOT re-serialised JSON
function verifyCamelAiWebhook(rawBody, get, secret = process.env.CAMEL_AI_WEBHOOK_SECRET) {
const prefix = get('webhook-id') ? 'webhook' : 'svix'; // fallback to svix-*
return new Webhook(secret).verify(rawBody, {
'webhook-id': get(`${prefix}-id`),
'webhook-timestamp': get(`${prefix}-timestamp`),
'webhook-signature': get(`${prefix}-signature`),
}); // returns the parsed event; throws → respond 400
}
Python: from standardwebhooks.webhooks import Webhook, then Webhook(secret).verify(await request.body(), {...same mapping...}).
Retries through Hookdeck: the library always enforces a fixed 5-minute timestamp window. Hookdeck retries and replays resend the original timestamp, so the library rejects late retries after 5 minutes. For a manual verifier where the window can be dropped or widened, see references/verification.md.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Envelope
{ "id": "evt_...", "type": "run.completed", "created": 1767225600, "data": { } }
id is also the webhook-id and is kept across retries, so dedupe on it. created is Unix seconds. Order by created, because retries can reorder events.
Common Event Types
| Event | Fired when |
|---|---|
run.started / run.completed / run.failed | An agent run starts, finishes or fails |
input.requested / input.resolved | A run asks for input / the input is answered |
usage.recorded | Usage is recorded |
voice.session.started / .ended / .error | A voice session starts, ends or errors |
voice.utterance / voice.speech.started / voice.speech.ended | Voice transcript and speech events |
billing.balance.low / billing.balance.depleted | The account balance is low / used up |
end_user.balance.low / end_user.allowance.low | An end user's balance or allowance is low |
end_ / .granted / .refunded / .adjusted | An end user's credit changes |
The docs list 20 types and say the list may not be exhaustive. Full details are in references/overview.md.
Delivery Semantics
- At least once. Return a
2xxwithin 10 seconds and process asynchronously. - Anything else is retried with exponential backoff (5 seconds to an hour apart) for 3 days.
- An endpoint receives runs that begin after it is created.
Environment Variables
CAMEL_AI_WEBHOOK_SECRET=whsec_xxxxx # full whsec_ value; handlers fail closed (500) without it
The secret is returned once by POST /v1/webhooks (or shown once in the console). POST /v1/webhooks/{webhookId}/secret replaces it, and the old one also signs for 24 hours.
Local Development
# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 camel-ai --path /webhooks/camel-ai
Register the printed URL with POST /v1/webhooks (or in the console) and start a new run.
Reference Materials
- references/overview.md - Event catalog, payload envelope, delivery
- references/setup.md - Endpoint registration, signing secret, rotation
- references/verification.md - Library and manual verification, header fallback, debugging