
Built a WhatsApp AI-to-human handoff system in n8n with Twilio Flex
Most WhatsApp bots I've seen have the same failure mode: the AI tells the user "I'm transferring you to a human" and then nothing happens. The conversation just dies. The user realizes they've been lied to and leaves.
So I built a proper handoff system in n8n that actually routes the conversation to a live agent through Twilio Flex, with full conversation history preserved. Here's how the flow works and where the rough edges are.
Architecture overview
The n8n workflow receives incoming WhatsApp messages via a Twilio webhook. First thing it does is extract the message data (from, to, body, context) and then fetch the current conversation state. Right now that state lives in n8n's `getWorkflowStaticData` method, which works fine for a proof of concept but has an obvious problem I'll get to.
After fetching state, the flow routes by **mode**: either `ai` or `human`.
In `ai` mode, the message goes to Claude Sonnet with a system prompt that includes FAQ data, pricing, schedules, and a specific instruction to append an `ESCALAR` tag to its response when it detects the conversation needs escalation. That tag is what the next node listens for.
If `ESCALAR` is present, the flow:
- Sends a waiting message to the customer ("you're being transferred")
- Grabs the last 10 messages as context
- Hits the Twilio Conversations API to close any existing active conversation for that number (Flex requires conversations to be in a fresh state before a human agent can pick them up)
- Creates a new conversation, injects the message history, adds both the customer and the agent as participants
- Creates a TaskRouter task with WhatsApp channel attributes, workspace SID, workflow SID, and routing properties
- That task shows up in the Flex UI for the agent to accept
Once the mode flips to `human`, the AI node is bypassed entirely. Incoming messages just get forwarded to the Flex conversation. The agent handles it from there.
What I'd change
Two things I'd refactor before using this in production:
First, `getWorkflowStaticData` is fine for demos but you're using the n8n instance itself as the source of truth. Restart the instance and you lose all conversation state. This should be a Postgres node, a Redis call, or even a Google Sheets write depending on the scale. Any external store works, just not the instance memory.
Second, right now the only way to move a conversation back from `human` mode to `ai` mode is to send a "reset" message, which is purely a dev convenience. For my client's use case this was acceptable since they wanted 100% human follow-through once escalation happened. But for a real product you'd want a proper re-entry mechanism, probably triggered from the agent side when they close the conversation in Flex.
On the Twilio Flex side
The TaskRouter piece is where it gets a bit involved. You need the Workspace SID, the Workflow SID, and the Flex Conversation Service SID (found under Conversations > Manage Services). The flow creates an interaction object that Flex uses to dispatch the task to available agents.
The pattern overall is what's called human-in-the-loop in agentic system design. Conceptually simple, surprisingly tricky to implement correctly because the naive version (AI says "transferring you" and doesn't actually do it) is everywhere.
Link to the YouTube video: https://www.youtube.com/watch?v=Y79DIv-CLI8. It includes the source code and walkthrough of each node and how everything works together.
Happy to take feedback on the content for improvements!