Skip to main content
Both Moda SDKs can turn a Vapi end-of-call report into conversation data: you host a webhook endpoint, Vapi posts the report when a call ends, and you pass the payload to one helper function. Use this page to set up the webhook handler in Node.js or Python and to see exactly what gets captured.

Prerequisites

  • moda-ai for Node.js (Node 18+) or Python (3.10+)
  • A webhook endpoint that Vapi can reach over HTTPS
  • A Moda API key, created at Settings → Ingestion keys (see Authentication)

Set up

1

Install the SDK

2

Create the webhook handler

Initialize Moda once, then pass each incoming payload to the end-of-call helper. Payloads that are not end-of-call reports are ignored without error, so it is safe to route all Vapi webhooks through the same handler — the isEndOfCallReport guard below is optional but makes the handler’s intent explicit.
3

Point Vapi at your endpoint

In Vapi, set your assistant’s server URL to the webhook endpoint and enable the end-of-call-report server message so Vapi posts the report when a call ends.
4

Verify in the dashboard

Complete a test call, then open Conversations in the dashboard. Within minutes of the call ending you should see a new conversation containing the caller and assistant turns as messages, with any tool calls captured as tool-call entries.
On serverless platforms (Vercel, Lambda, Cloudflare Workers), call await Moda.flush() (Node.js) or moda.flush() (Python) before returning the response. The runtime can freeze or terminate after the response is sent, and unflushed data is lost. Long-running servers flush automatically on the batch interval; flush once more on shutdown.

Function reference

Both functions are available on the Moda object and as top-level imports in Node.js, and at module level in Python. Options let you override the identifiers recorded on the call:

What gets captured

Each processed report produces one trace with a parent span for the call and child spans for its parts, so a call and its turns appear together as one conversation. Turns are read from the report’s call.artifact.messages; if the artifact has no messages, a transcript array of {role, message} entries on the report is converted instead.

Troubleshooting

Next steps