# Voiceflow Webhooks

[Voiceflow](https://www.voiceflow.com) is an AI-agent building platform. Its organization events
webhooks report project and project-environment lifecycle changes (created, published, merged,
deleted). They are delivered through Svix, which implements Standard
Webhooks. Every delivery is an HTTP `POST` with a JSON body and three signature headers:
`svix-id`, `svix-timestamp`, and `svix-signature`. The body is `{type, data, resource, time}`:
`type` is the event name, `data` holds the details, `resource` is `organization-<organizationID>`,
and `time` is Unix milliseconds.

## Only organization event webhooks are supported

Hookdeck's configuration alert for this source reads: "Only organization event webhooks are
supported. The project-level session webhook doesn't document this signing scheme and may fail
verification."

Set the URL under **Settings > Organization** in the Voiceflow dashboard. That webhook is
signed by Svix with the scheme below. Don't point this handler at the project-level session
lifecycle webhook, because Voiceflow doesn't document a signing scheme for it. See
[references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/voiceflow-webhooks/references/setup.md).

## When to Use This Skill

* How do I receive Voiceflow webhooks?
* How do I verify Voiceflow webhook signatures (`svix-signature`)?
* How do I handle `organization.project.created` or `organization.project.environment.published` events?
* How do I react to a Voiceflow environment being merged or deleted?
* Why is my Voiceflow 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.VOICEFLOW_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')); // {type, data, resource, time}; parse only after verify passes

```

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

wh = Webhook(os.environ["VOICEFLOW_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/voiceflow-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/voiceflow-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/voiceflow-webhooks/examples/fastapi/).

The secret appears under **Settings > Organization** once a webhook URL is set, and there is one
per organization webhook URL. Voiceflow doesn't print an example value. Svix secrets look like
`whsec_<base64>`, so keep the full value in `VOICEFLOW_WEBHOOK_SECRET`. Regenerating the secret
keeps the old one valid for 24 hours. Handlers fail closed: with the variable unset they return 500
instead of accepting anything.

### 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/voiceflow-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

Six events are documented (the list may not be exhaustive). The name is `type` in the JSON body:

| Event | Triggered When |
| --- | --- |
| `organization.project.created` | A project is created in the organization |
| `organization.project.deleted` | A project is deleted |
| `organization.project.environment.created` | An environment is created (branched from `data.source`) |
| `organization.project.environment.published` | An environment is published (`publishedVersionIDBefore` / `publishedVersionIDAfter`) |
| `organization.project.environment.merged` | One environment is merged into another |
| `organization.project.environment.deleted` | An environment is deleted |

See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/voiceflow-webhooks/references/overview.md) for the payload shapes.

## Environment Variables

```bash
# Organization webhook signing secret (Svix format, starts with whsec_)
VOICEFLOW_WEBHOOK_SECRET=whsec_xxxxx

```

## Local Development

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

```

Set the tunnel URL under Settings > Organization in the Voiceflow dashboard, then trigger an
event (for example, create a project) to see it arrive.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/voiceflow-webhooks/references/overview.md) - Voiceflow organization webhook concepts, events, payload shapes
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/voiceflow-webhooks/references/setup.md) - Set the organization webhook URL, get the secret, org vs session webhooks
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/voiceflow-webhooks/references/verification.md) - Svix verification details, header fallback, manual verifier, timestamp window