Build a Voice Receptionist for Your Business · 30 min · Vapi · Google Sheets API · Railway
Build
The capstone project
Every piece works. Today closes the loop: every call gets logged somewhere durable, and the webhook server moves off your laptop onto something that stays up when you close your terminal.
Logging every call, not just the ones you happened to be watching
app.post('/webhooks/call-end', async (req, res) => {
const { call, transcript, endedReason } = req.body.message;
await appendToGoogleSheet({
timestamp: new Date().toISOString(),
callerNumber: call.customer?.number ?? 'unknown',
duration: call.endedAt && call.startedAt
? (new Date(call.endedAt).getTime() - new Date(call.startedAt).getTime()) / 1000
: null,
outcome: endedReason,
transcriptSummary: transcript?.slice(0, 500),
});
res.json({ received: true });
});
This webhook fires when a call ENDS, regardless of how it ended (hung up, completed booking, transferred) -- meaning every single call gets logged, not just the ones you're actively monitoring. This is the difference between "I checked a few calls manually" and "I have a real record of every call this receptionist has ever handled."
From ngrok to a real deployment
ngrok URLs are temporary -- this is not a deployment, it's a testing tunnel
Every time you restart ngrok (or your laptop sleeps, or your session times out), the public URL changes, and Vapi's webhook configuration silently points at a dead endpoint until you update it. A receptionist that stops working whenever your laptop closes isn't a business phone line. Deploying the webhook server to Railway (or any persistent host) with a stable URL is the actual difference between "I got this working once" and "this runs."
You deploy your webhook server to Railway and update Vapi's server URL to point at it. A test call works fine, but you're wondering how to know if this stays reliable over the next week without you actively watching it. What's the actual answer, from earlier in this lesson?
This connects directly to the beginning of this lesson: the call logger doesn't just satisfy a course requirement, it's the actual mechanism for knowing your deployed receptionist is working over time without babysitting it — check the Google Sheet periodically and you have a real record, not a guess.
Ship it
Deploy the webhook server, confirm Vapi points at the stable production URL, and make one final real call: book a real appointment, receive a real SMS, and confirm the call appears in your Google Sheet log — with your RAM/CPU or a simple "it's alive" check visible.
Ship Prompt: Call the receptionist! [share the number]. What's the weirdest thing a caller (or you, testing) asked that it handled well?