# camelAI Webhooks

[camelAI](https://camelai.com)'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)

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

```javascript
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](https://github.com/hookdeck/webhook-skills/blob/main/skills/camel-ai-webhooks/references/verification.md).

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/camel-ai-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/camel-ai-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/camel-ai-webhooks/examples/fastapi/).

## Event Envelope

```json
{ "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_user.credit.exhausted` / `.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](https://github.com/hookdeck/webhook-skills/blob/main/skills/camel-ai-webhooks/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

```bash
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

```bash
# 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](https://github.com/hookdeck/webhook-skills/blob/main/skills/camel-ai-webhooks/references/overview.md) - Event catalog, payload envelope, delivery
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/camel-ai-webhooks/references/setup.md) - Endpoint registration, signing secret, rotation
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/camel-ai-webhooks/references/verification.md) - Library and manual verification, header fallback, debugging