Puzzle #17 — Voiceflow Reset Code Extension
Custom Form Extension + n8n Email Workflow · Built in Bolt.new
Coded inside Bolt.new
Custom Voiceflow Form Extension
n8n Webhook → Email Workflow
JS resetFormExtension.js
const ResetFormExtension = { name: 'ResetFormExtension', type: 'response', // Match the custom trace from Voiceflow Function step match: ({ trace }) => trace.type === 'ext_resetForm' || trace.payload?.name === 'ext_resetForm', render: ({ trace, element }) => { // Build form HTML const formContainer = document.createElement('form'); formContainer.innerHTML = ` <label style="font-size:12px;color:#64748b"> Email Address </label> <input type="email" name="email" placeholder="you@example.com" required /> <button type="submit"> Send Reset Code </button> `; // Handle form submit → POST to n8n webhook formContainer.addEventListener('submit', async (e) => { e.preventDefault(); const email = formContainer .querySelector('input').value; await fetch( 'https://YOUR-N8N.app/' + 'webhook/reset-code', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }) } ); // Notify Voiceflow: form complete window.voiceflow.chat .interact({ type: 'complete', payload: { email } }); }); element.appendChild(formContainer); } };
⚡ bolt-index.html — Widget Registration
// Inside your Bolt.new index.html // Load the Voiceflow Chat Widget <script type="text/javascript"> (function(d, t) { var v = d.createElement(t); v.src = "https://cdn.voiceflow.com/" + "widget/bundle.mjs"; v.type = "text/javascript"; v.onload = function() { window.voiceflow.chat.load({ verify: { projectID: 'YOUR_PROJECT_ID' }, url: 'https://general-runtime' + '.voiceflow.com', versionID: 'production', // Register the extension! assistant: { extensions: [ ResetFormExtension ] } }); }; d.head.appendChild(v); })( document, "script" ); </script>
VF Voiceflow Function Step — Trace Output
// In your Voiceflow Function step, // add this code to emit the trace // that triggers the form extension: export default async function main({ trace }) { trace.speak( 'Please enter the email ' + 'associated with your account ' + 'to reset your password:' ); // Emit custom trace for extension trace.addTrace({ type: 'ext_resetForm', payload: { name: 'ext_resetForm' } }); }
Webhook
trigger · POST
📥 1 item
Method POST
Path /webhook/reset-code
Body { email }
Auth None
POST
{ }
Generate Reset Code
code · JavaScript
🔑 1 item
Mode Run Once for All Items
Language JavaScript
Code:
const chars = 'ABCDEFabcdef0123456789'; let code = ''; for (let i = 0; i < 5; i++) { code += chars[ Math.floor(Math.random() * chars.length) ]; } return [{ json: { email: $input.first().json.body.email, resetCode: code } }];
1 item
{ } n8n Code Node — JavaScript
// n8n Code Node: Generate Reset Code // Receives: { body: { email: "user@example.com" } } const chars = 'ABCDEFabcdef0123456789'; let code = ''; for (let i = 0; i < 5; i++) { code += chars[ Math.floor( Math.random() * chars.length ) ]; } return [{ json: { email: $input.first().json.body.email, resetCode: code } }];
Your AI agent
🔇 ↻ ✕
❖
Your AI agent
How can I help you today?
Please enter the username and email associated with your account to reset your password:
Email Address
✓
Reset code sent!
Check your inbox for the code.
Chat has ended
1
Create the Voiceflow Agent

Set up a new Voiceflow agent (or use an existing one). In the canvas:

  • Add a Function step that outputs a custom trace of type ext_resetForm
  • The Function step code uses trace.addTrace() to emit the trace that the form extension will match on
  • Add a Text step before it saying: "Please enter the email associated with your account to reset your password"
  • After the Function step, add a Capture step that waits for the complete interaction from the extension
// Function step code export default async function main({ trace }) { trace.addTrace({ type: 'ext_resetForm', payload: { name: 'ext_resetForm' } }); }
2
Build the n8n Webhook Workflow

In your n8n instance, create a new workflow with 3 nodes:

  • Webhook node — Set method to POST, path to /webhook/reset-code. This receives { email } from the form extension.
  • Code node — Generate a random 5-character alphanumeric reset code using JavaScript.
  • Gmail node — Connect your Gmail credentials. Set the To field to {{ $json.email }}, subject to "Reset Code", and body to include {{ $json.resetCode }}.

Activate the workflow and copy the webhook production URL.

3
Code the Extension in Bolt.new

In Bolt.new, create a new project with an index.html file:

  • Define the ResetFormExtension object with type: 'response', match, and render functions
  • In the render function, create an email input form and handle submission with a fetch() POST to your n8n webhook URL
  • After the fetch succeeds, call window.voiceflow.chat.interact({ type: 'complete', payload: { email } })
  • Load the Voiceflow widget script and register the extension in assistant.extensions
4
Test & Verify

Run the Bolt.new preview and interact with the Voiceflow chat widget:

  • The agent displays the password reset message
  • The custom form extension renders inline with an email input field
  • Enter an email address and click "Send Reset Code"
  • The extension POSTs to n8n → code is generated → email is sent via Gmail
  • Check the inbox for the "Reset Code" email containing the random 5-character code
  • The agent displays "Check your email" → Chat ends
👤 User enters email
in Voiceflow Chat
→
🔌 Form Extension
captures & POSTs
→
📥 n8n Webhook
receives { email }
→
🔑 Code Node
generates code
→
✉️ Gmail
sends email
→
📬 User Inbox
receives code
🔌
1. Voiceflow Response Extension

The ResetFormExtension is a custom response-type extension registered in the Voiceflow chat widget configuration. It works by:

  1. Matching a custom trace (ext_resetForm) emitted by the Voiceflow Function step
  2. Rendering an email input form directly inside the chat widget's message area using the element DOM node
  3. Handling submission by making a fetch() POST request to the n8n webhook with the email address
  4. Notifying Voiceflow via window.voiceflow.chat.interact() to continue the conversation flow
⚙️
2. n8n Webhook → Code → Email Pipeline

The n8n workflow is a 3-node linear pipeline:

  1. Webhook Node: Listens for POST requests at /webhook/reset-code. Receives { email: "user@example.com" } from the form extension's fetch() call.
  2. Code Node: Generates a random 5-character alphanumeric reset code using a simple JavaScript loop. Passes { email, resetCode } to the next node.
  3. Gmail Node: Sends an email using configured Gmail credentials. The recipient is {{ $json.email }}, subject is "Reset Code", and the body contains the generated code.
⚡
3. Bolt.new Hosting & Deployment

The entire frontend — including the Voiceflow widget script, the custom extension definition, and any styling — is hosted as a static HTML page in Bolt.new. Key points:

  1. Single file deployment: The extension code lives inline in index.html alongside the Voiceflow widget loader
  2. No backend needed: The form extension directly calls the n8n webhook URL — Bolt.new only serves the static page
  3. Hot preview: Bolt.new's WebContainer provides instant live preview while coding the extension
  4. CORS: n8n webhooks accept cross-origin requests by default, so no additional proxy is needed
📖
4. The Clue — Voiceflow Docs

The puzzle clue says: "Visit the Voiceflow Docs on how to create custom extensions." The Voiceflow documentation describes two types of extensions:

  1. Response Extensions: Render custom UI elements inside the chat widget (forms, maps, video players). They define a match function to detect custom traces and a render function to build the DOM.
  2. Effect Extensions: Trigger site-wide side effects (confetti, input disable/enable, analytics events) without rendering visual elements in the chat.

For this puzzle, we use a response extension because we need to render an interactive email form directly inside the chat message area. The key insight from the docs is the match → render → interact lifecycle.