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.failed or input.requested events?
  • Why is my camelAI webhook-signature verification failing?
  • Why do Hookdeck retries of camelAI webhooks fail verification?

Verification (core)

HeaderValue
webhook-idThe event id (evt_...), the same on every retry
webhook-timestampUnix seconds of this delivery attempt
webhook-signatureOne 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

EventFired when
run.started / run.completed / run.failedAn agent run starts, finishes or fails
input.requested / input.resolvedA run asks for input / the input is answered
usage.recordedUsage is recorded
voice.session.started / .ended / .errorA voice session starts, ends or errors
voice.utterance / voice.speech.started / voice.speech.endedVoice transcript and speech events
billing.balance.low / billing.balance.depletedThe account balance is low / used up
end_user.balance.low / end_user.allowance.lowAn end user's balance or allowance is low
end_user.credit.exhausted / .granted / .refunded / .adjustedAn 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 2xx within 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 13, 2026

View on GitHub →