Agent skill
Voiceflow Webhooks Skill
Receive and verify Voiceflow organization event webhooks (AI agent platform, delivered via Svix). Use when setting up Voiceflow webhook handlers, debugging svix-signature verification, or handling events like organization.project.created, organization.project.environment.published, and organization.project.environment.merged.
Install this skill
npx skills add hookdeck/webhook-skills --skill voiceflow-webhooks
Voiceflow 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.
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.createdororganization.project.environment.publishedevents? - 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.
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
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/, examples/nextjs/, 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, 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 for the payload shapes.
Environment Variables
# Organization webhook signing secret (Svix format, starts with whsec_)
VOICEFLOW_WEBHOOK_SECRET=whsec_xxxxx
Local Development
# 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 - Voiceflow organization webhook concepts, events, payload shapes
- references/setup.md - Set the organization webhook URL, get the secret, org vs session webhooks
- references/verification.md - Svix verification details, header fallback, manual verifier, timestamp window