Sync Vapi voice AI call state into Convex reactively via webhooks, and place outbound calls from Convex actions with live status, transcript, and cost tracking.
npm install convex-vapiconvex-vapi syncs Vapi voice AI call state into your Convex database via server-URL webhooks, enabling reactive queries on call status, transcript, summary, and cost without polling. It also exposes a `createCall` action for placing outbound calls directly from Convex, recording the call immediately before the first webhook arrives. Webhook payloads are verified with constant-time secret comparison and deduplicated via SHA-256 body hashing, with non-destructive merges ensuring richer data from earlier events is never overwritten by sparser later ones.
convex-vapi exposes a createCall action that calls the Vapi REST API and immediately records the call in Convex. It returns { callId, status } so you can reference the call in queries before the first webhook arrives, without polling or waiting.
convex-vapi mounts a webhook handler at any HTTP route in your Convex deployment. Each incoming status-update and end-of-call-report event patches the corresponding row in the component's calls table, so useQuery re-renders automatically as a call moves through queued, ringing, in-progress, and ended states.
The convex-vapi webhookHandler verifies every inbound request against the X-Vapi-Secret header using a constant-time string comparison before writing anything to the database. You configure a shared secret on both the Vapi assistant's server.secret field and as a Convex environment variable named VAPI_WEBHOOK_SECRET.
convex-vapi provides a refreshCall action that fetches the current call state directly from the Vapi API and re-records it, including transcript, summary, and recording URL if the call has ended. This works as a fallback for missed webhook deliveries without any manual database manipulation.
No. convex-vapi is a Convex component, so its calls and webhookEvents tables live in an isolated namespace separate from your app's convex/schema.ts. You add it via app.use(convexVapi) in convex.config.ts and access its data only through the functions the component exposes.
convex-vapi processes two event types: status-update, which updates the call's status field as the call progresses, and end-of-call-report, which records endedReason, transcript, summary, recordingUrl, and cost once a call ends. All other event types are recorded for idempotency but otherwise ignored.
Because Vapi does not include a per-delivery ID in its webhook headers, convex-vapi hashes the raw request body with SHA-256 and stores the result as an eventId in the webhookEvents table. An identical payload delivered twice is detected as a duplicate and skipped before any database write occurs.
No. convex-vapi merges webhook payloads non-destructively: fields that are missing from the current event fall back to whatever was already stored in the database. A sparser event like a mid-call status-update will never blank out richer data like a transcript that an earlier end-of-call-report already recorded.
You need to install the package with npm install convex-vapi, register the component in convex/convex.config.ts, set the VAPI_API_KEY and VAPI_WEBHOOK_SECRET environment variables, mount the webhookHandler on an HTTP route in convex/http.ts, and configure your Vapi assistant's serverUrl and server.secret to match. No schema changes to your app are needed.