# Pipecat Cloud Webhooks

Pipecat Cloud ([pipecat.daily.co](https://pipecat.daily.co/)) signs every webhook delivery
following Standard Webhooks, with the delivery provider's
`svix-*` header names. Each delivery is an HTTP `POST` with a signed JSON body and three headers:
`svix-id`, `svix-timestamp`, and `svix-signature`. The body is an envelope:
`{ "event_type": "build.succeeded", "org": "acme-co", "created_at": "2026-08-25T11:40:32.000Z", "data": { ... } }`.
`org` is the organization name and `created_at` is ISO 8601 UTC. The body has no event id (use
`svix-id`), and no header carries the event type.

## When to Use This Skill

* How do I receive Pipecat Cloud webhooks?
* How do I verify Pipecat Cloud webhook signatures (`svix-signature`)?
* How do I handle `build.succeeded`, `deployment.updated` or `session.started` events?
* How do I react to `spend.threshold_reached`, `spend.limit_reached` or `service.suspended`?
* Why is my Pipecat Cloud webhook signature verification failing, or rejecting Hookdeck retries?

## Verification (core)

Signed content is `{svix-id}.{svix-timestamp}.{raw body}`. Svix signs it with HMAC-SHA256 using
the base64-decoded secret (the part after `whsec_`), base64-encodes the result, and sends one or
more space-separated `v1,<signature>` entries. Use the official `svix` library, which compares in
constant time. Read the `svix-*` headers, and when `svix-id` is absent fall back to the `webhook-*`
headers (`webhook-id`, `webhook-timestamp`, `webhook-signature`), as Hookdeck does.

```javascript
import { Webhook } from 'svix'; // svix 2.x is ESM-only

const prefix = req.headers['svix-id'] ? 'svix' : 'webhook';
const id = req.headers[`${prefix}-id`];
const timestamp = req.headers[`${prefix}-timestamp`];
const signature = req.headers[`${prefix}-signature`];
// rawBody: the raw Buffer/string, never re-serialized JSON
new Webhook(process.env.PIPECAT_CLOUD_WEBHOOK_SECRET).verify(rawBody, {
  'svix-id': id, 'svix-timestamp': timestamp, 'svix-signature': signature,
  'webhook-id': id, 'webhook-timestamp': timestamp, 'webhook-signature': signature,
}); // throws on a bad signature or a timestamp outside the window
const event = JSON.parse(rawBody.toString('utf8')); // parse only after verify passes

```

```python
from svix.webhooks import Webhook, WebhookVerificationError

wh = Webhook(os.environ["PIPECAT_CLOUD_WEBHOOK_SECRET"])  # "whsec_..."
wh.verify(raw_body, {"svix-id": id, "svix-timestamp": ts, "svix-signature": sig,
                     "webhook-id": id, "webhook-timestamp": ts, "webhook-signature": sig})

```

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

Each endpoint has one signing secret, revealed with the key icon on the endpoint's row under
Settings > Webhooks in the Pipecat Cloud dashboard. It looks like `whsec_...`. Keep the full value
in `PIPECAT_CLOUD_WEBHOOK_SECRET`. Deleting and recreating an endpoint issues a **new** secret.
Handlers fail closed: with the variable unset (or not a `whsec_` value) they return 500 instead
of accepting anything. Pipecat Cloud's own sample uses the `standardwebhooks` library after
mapping `svix-*` to `webhook-*`; the `svix` library used here reads either set, so no mapping
is needed beyond the fallback shown.

### Hookdeck retries and replays hit the 5-minute window

The `svix` and `standardwebhooks` libraries always enforce a fixed 5-minute timestamp window, and
it can't be configured. Hookdeck retries and replays resend the **original** `svix-timestamp`, so
the library rejects any retry or replay that happens more than 5 minutes after the original
delivery. If you verify behind Hookdeck (or replay old events), use the manual verifier in
[references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/pipecat-cloud-webhooks/references/verification.md), where the window can be dropped or
widened. Deduplicate on `svix-id` instead of relying on the timestamp.

## Common Event Types

20 events are documented (the list may not be exhaustive). The name is `event_type` in the JSON
body, and the examples handle all of them:

| Event | Triggered When |
| --- | --- |
| `build.started` | A build enters `building` (best-effort: fast builds may emit only a terminal event) |
| `build.succeeded` | A build succeeds; `data.image_uri` names the image |
| `build.failed` | A build fails or is stopped, or the trigger call failed |
| `build.timeout` | A build times out |
| `deployment.created` | A service's first deployment |
| `deployment.updated` | A redeploy replaced the running pods |
| `service.suspended` | Service torn down: `reason` is `trial_exhausted` or `account_suspended` |
| `service.resumed` | A suspension was lifted |
| `spend.threshold_reached` | Spend crossed 80% or 90% of the limit |
| `spend.limit_reached` | Spend reached 100% of the limit |
| `spend.limit_cleared` | The spend limit was removed |
| `session.started` | An agent session starts (service name is `service`, not `service_name`) |
| `org.trial_started` / `org.trial_ended` | A trial code is redeemed / trial credits run out |
| `api_key.created` / `.rotated` / `.deleted` | An API key changes (the key value is never sent) |
| `secret.created` / `.updated` / `.deleted` | A secret set or key changes (values are never sent) |

See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/pipecat-cloud-webhooks/references/overview.md) for the full catalog and payload shape.

## Environment Variables

```bash
# Signing secret of the webhook endpoint (Svix format, starts with whsec_)
PIPECAT_CLOUD_WEBHOOK_SECRET=whsec_xxxxx

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 pipecat-cloud --path /webhooks/pipecat-cloud

```

Register the tunnel URL as an endpoint under Settings > Webhooks in the Pipecat Cloud dashboard,
then use the endpoint's **send test event** action (deliveries carry a `webhook-test: true`
header) or trigger a real build.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/pipecat-cloud-webhooks/references/overview.md) - Pipecat Cloud webhook concepts, events, payload shapes, delivery
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/pipecat-cloud-webhooks/references/setup.md) - Register an endpoint, get the secret, send a test event
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/pipecat-cloud-webhooks/references/verification.md) - Svix verification details, header fallback, manual verifier, timestamp window