Module 20 · ai · intermediate
Solutions 20 · WhatsApp AI Assistant
None of these have been run against live Google, Slack or WhatsApp accounts yet.
Exercise 1
Replace the No Operation node with Google Sheets Append Row (sheet Leads, columns received_at, wa_id, name, service, zip, preferred_time, chat):
| Column | Value |
|---|---|
| received_at | {{ $now.toISO() }} |
| wa_id | {{ $json.wa_id }} |
| name | {{ $json.lead.name }} |
| service | {{ $json.lead.service }} |
| zip | {{ $json.lead.zip }} |
| preferred_time | {{ $json.lead.preferred_time }} |
| chat | https://wa.me/{{ $json.wa_id }} |
It runs once per booking: new_lead is true only on the turn that completes the lead (lead_captured_at is set once), and retried webhooks are duplicate, so they never reach this branch.
Exercise 2
Replace the No Operation node with:
HTTP Request GET http://localhost:5678/webhook/wa/conversation?wa_id={{ $json.wa_id }}
→ Slack Send Message (channel #whatsapp):
{{ $('Needs Staff?').item.json.action === 'emergency' ? '🚨 URGENT safety message' : $('Needs Staff?').item.json.action === 'silent' ? '💬 New message (you have this chat)' : '🙋 Customer wants a person' }} from {{ $json.name || $json.wa_id }}
{{ $json.history.slice(-4).map(h => (h.role === 'user' ? '👤 ' : '🤖 ') + h.text).join('\n') }}
Reply in WhatsApp Business, then hand back: POST /webhook/wa/handoff/release {"wa_id":"{{ $json.wa_id }}"}
Exercise 3
- Add Schedule Trigger
Nudge Every Hourand a fifth Route rule{{ $('Nudge Every Hour').isExecuted }}→ output nudge. - Code
Find Stuck Bookings:const db = $getWorkflowStaticData('global'); const now = new Date(); const hoursAgo = (iso) => (now - new Date(iso)) / 3600e3; const out = []; for (const c of Object.values(db.convos ?? {})) { if (!c.awaiting || c.opted_out || c.nudged_at || c.lead_captured_at) continue; if (c.handoff_until && c.handoff_until > now.toISOString()) continue; if (hoursAgo(c.last_inbound_at) < 2 || c.window_open_until < now.toISOString()) continue; c.nudged_at = now.toISOString(); out.push({ json: { wa_id: c.wa_id, reply: `Hi${c.lead.name ? ' ' + c.lead.name.split(' ')[0] : ''}, still want us to book that ${c.lead.service ?? 'visit'}? Just reply here and we'll pick up where we left off.` } }); } return out; - → WhatsApp Send (same settings as the reply node, On Error → Continue).
- In
apply.js, clearc.nudged_atwhenever the customer writes again, so a later stall can be nudged once more.
Marking nudged_at before the send means a failed send isn't retried; move it after the send (as in module 19's drip) if you want retries.
Stretch
For type === 'audio': WhatsApp Media → Download (media id {{ $json.audio.id }}, which you'd add to parse.js) → OpenAI Audio → Transcribe → set text to the transcript and continue into Conversation State. Keep the "please type" reply as the fallback when transcription fails.