Build a Trick or Treat Chatbot with Twilio Studio and Functions, and Node.js

September 29, 2021
Written by
Reviewed by


With Halloween around the corner, I decided I wanted to build a chatbot to help people find trick or treat locations in their neighborhood. The idea was that a user could send an SMS to the chatbot’s phone number and interact with the bot to either find trick or treat locations, or add their location to the list of places giving out candy or hosting spooky events.

In this article, you’ll learn how to build a chatbot like mine that you can customize however you like.


To get started with this tutorial, you’ll need the following:

Overview of the chatbot architecture

The chatbot you’re about to build will use the following technologies:

In my chatbot, I also used Airtable for my database in order to store my users’ responses. This article will not cover database integration, but it will specify where you can connect with the database of your choosing. The Airtable integration is covered in part two of this project.

Twilio Studio is a drag-and-drop, no-code application builder that allows you to create all kinds of communication apps. It works by providing a blank canvas where you can drag and drop configurable widgets that handle different tasks. You can connect these widgets together to create application flows.

Twilio Functions is a serverless Node.js environment where you can host application code.

The chatbot, built with Studio, will collect responses from your users and depending on what the user wants to do, will send their responses to Functions to be processed - this is like a webhook.

To build the chatbot, you’ll create the Functions portion of the app first. This way, when you get to the Studio portion, your code will be complete and you can configure and connect your widgets without having to jump back and forth between Functions and Studio.

Create your Functions Service

Navigate to the Functions section of the Twilio Console. Click the blue Create Service button and when prompted, give your service a brief, human-readable name like trick-or-treat or halloween. Then click Next.

This will open the editor for your new service. A Functions Service is a collection of assets, functions, and environment details (like variables or dependencies).

Your chatbot needs to be able to do two things: lookup trick-or-treat locations based on the user’s submitted zip code, and store trick-or-treat locations added by users. You’ll create one function for each of these tasks.

Create functions

Click the blue Add + button and then select Add Function to create a new function. In the input field, change the path name to /find-candy. Leave the visibility status as protected. Repeat this process to create a second function with the path /give-candy.

Screenshot of functions editor showing the left side and top of editor where the user can add a new function

Find candy

The Studio workflow will make a POST request to the /find-candy function after the user has both indicated they are looking to find trick or treat locations and submitted their zip code. The user’s zip code will be passed in the body of the request. This zip code will be available on the function’s event object.

Click on the /find-candy function in the list of functions and the code editor for this function will open to the right. Delete any stock code visible in the editor and replace it with the following:

exports.handler = async function(context, event, callback) { 
  /* This is a placeholder list of addresses submitted by users.
   * In your app, you should connect with your database and lookup all address submissions
   * with the zipcode passed to the function in event.zipcode
  const allLocations = [
      zipcode: '12345',
      address: '555 Oak Street'

  const localLocations = allLocations.filter(loc => loc.zipcode == event.zipcode).join('\n');

  const msg = localLocations.length == 0 
    ? 'bOoOoOo there are no haunted locations in your area. Perhaps you should start the spOoOoky festivities.'
    : `Find your treats (or your tricks!) at these nearby haunted locations: \n${localLocations}`

  const twiml = new Twilio.twiml.MessagingResponse();
  return callback(null, twiml);

As you’re building your chatbot, you’ll want to connect to your database at the beginning of this function and lookup all addresses associated with the provided zip code. As shown in the code above, you’ll then return a TwiML message with the list of local trick-or-treat spots, or a message that lets the user know there’s no locations nearby. For testing purposes, feel free to add more fake locations to the allLocations array.

Be sure to click the Save button beneath the editor.

Give out candy

When the user has indicated that they want to add their location to the list of trick-or-treat locations, the chatbot will prompt them for their zip code, and then in a second prompt, for their street address or cross streets. After these two responses have been collected in Studio, the workflow will make a POST request to the /give-candy function and pass these two data points in the request body.

Click on the /give-candy function to open its editor. Replace any placeholder code with the following:

exports.handler = async function(context, event, callback) {  
  const zip = event.zipcode;
  const address = event.address;
   * Here, you would connect to your database and insert the user 
   * provided zip code and address, found in the two variables above

  const msg = `Thanks for your contribution!`;
  const twiml = new Twilio.twiml.MessagingResponse();
  return callback(null, twiml);

In this function, you will access the user-provided zip code and address via event.zipcode and event.address respectively. You can then insert those values in your database, and on successful insertion, you can return a “thank you” TwiML message to the user.

Click Save beneath the editor. Then, at the bottom of the screen, click the blue Deploy All button to deploy your function service.

Screenshot showing Deploy All button at the bottom of the screen

Build your Studio flow

With your function service deployed, it’s time to build out your Studio flow.

Navigate to the Studio section of the Twilio Console. Click the + button or the button that says Create Flow to create a new Studio flow. Give it a short, human-readable name (one that matches your Functions service name is fine). Then, when prompted to choose a template, pick the Start from scratch option, and click Next.

Screenshot of studio flow options

This will open the canvas for your new Studio flow.

screenshot showing blank studio flow canvas with menu along the top and widget editor on the right

In this image you can see the Trigger widget, outlined in red, along the top of the canvas. On the right side, you can see the Widget Library. This library can be collapsed to a tiny bar by clicking the Hide button on the top right of the library.

The Widget Library is where you will find all the widgets you need for the chatbot flow. As you scroll through the list, when you find one you want to use, drag the title to the canvas and the widget will appear. You can drag the widget around the canvas to place it where you want for visual organization. When a widget is selected, the widget library will convert to a widget configuration space where you can edit the widget’s actions and properties.

Here is an overview of what the completed flow will look like:

screenshot of complete overview of studio flow canvas

I will walk through an overview of each part of this flow and provide basic information on using Studio, starting with the Trigger widget.

Chatbot initiation

When a user texts your Twilio phone number, the Studio workflow will be engaged (you’ll form this connection in the final step).

The user can text anything to the chatbot to get started. Once the chatbot is engaged, you want it to send a response prompting the user for their desired task (finding or giving candy).

To create this flow, look for the Send & Wait For Reply widget in the Widget Library. When you find it, drag it to the canvas beneath the Trigger widget.

To connect the two widgets, drag the red-filled circle beneath the Incoming Message option on the Trigger widget to the grey-filled circle in the top-left corner of the new Send & Wait For Reply widget.

Congratulations, you’ve performed your first widget connection!

Now select your new widget, and take a look at the Widget Library/configuration editor. Change the widget name to greeting and in the Message Body input, type in the message you want sent to your users once they engage the chatbot. Mine was “Happy Halloween! What ghoulish activities can I help you with?” Then click the Save button.

Screenshot showing configuration for greeting widget

Any Save & Wait For Reply widget will send a message to the user and then capture their reply. In later widgets, you can access this reply by referencing the widget name, but in this case, you want to save the reply value to a variable that can be overwritten by future replies.

To do this, find the Set Variables widget in the Widget Library and drag it beneath the greeting widget. Connect the Reply option of the greeting widget to your new widget.

Configure the widget name to set-task-variable, and in the configuration editor, add a new variable by clicking the + button. The Key field is where you name the variable. Call it activity.

The Value field is where you assign the reply value to the variable. To access the value in this field, use {{ widgets.greeting.inbound.Body }}. This syntax is liquid templating syntax.

Screenshot showing configuration for set-task-variable widget

Save the widget. At this point your widget layout should look like similar to this:

Screenshot showing trigger, reply, and variable storage widgets

Process user input

At this point, your user has been greeted by the chatbot, has sent a reply, and the reply has been stored in a variable. Now you need to branch out the flow based on what they replied.

There’s four reply classifications that you’ll want to handle for this part of the flow:

  1. The user indicates that they want to find trick-or-treat locations
  2. The user indicates they want to list their location as a trick-or-treat option
  3. The user indicates they want to exit the bot
  4. The user sends a reply that doesn’t fit into any of the first three classifications

Studio does not help you create AI powered bots, so you can’t train it on sample responses to classify the user’s reply for you.

Instead, you should generate a list of potential replies for each of the first three response classifications (find candy, give candy, exit). You’ll then input these lists into a Split Based On… widget.

Here’s the list of sample responses I used:

Response classification

Sample responses

find candy

trick or treat, find candy, i want to find candy, i want to go trick or treating, trick'r treat, where can i find candy, where can I go trick or treating

give out candy

give out candy, give candy, host halloween, meet trick or treaters, add my location, add location


exit, stop, no, cancel, quit

Find the Split Based On… widget in the widget library and drag it to the right of the widgets you’ve added so far.

Before configuring the new Split Based On… widget, create a connection from the Next option of the set-task-variable widget to the new Split Based On… widget.

Select the new widget, and then in the widget configuration panel, change the Variable to Test value to flow.variables.activity. Any variables created in your studio flow can be accessed via the flow.variables object.

You’ll edit the Transitions section of the configuration panel shortly. Before that though, add widgets for each of the four options. These should be configured as follows:

Widget type

Widget name

Message body

Send and Wait for Reply


bOooOooOooo I'm not sure how to help you. Try "find candy", "trick or treat", "give out candy", or "exit".

Send and Wait for Reply


How spOooOoOooky of you. Help me conjure up your local haunts: what's your zip code?

Send and Wait for Reply


How spOooOoOooky of you. What's your zip code?

Send Message


Thank you and Happy Halloweeeeeeen!

Whatever message you choose to send back to your users in the case of an unclear response, be sure that it contains hints about what constitutes a good response and use specific examples from your configured options.

Line up the first three widgets neatly in a horizontal line below the split_1 widget, and move the exit widget down and to the right so it’s out of the way.

With the widgets created, you can now add the connections/transitions from the split_1 widget, as described in the next step.

Set up transitions for the split_1 widget

Select the split_1 widget, and then in the configuration panel, select Transitions to configure the connections.

There will be an option for If No Condition Matches already. From the drop down, select your unclear-response widget.

screenshot showing transition configuration for the unclear-response widget

Then, add a new condition by clicking the + button next to New Condition.

In the new condition editor, select Matches Any Of from the first drop down, and then enter your list of sample responses for finding candy, separated by commas, in the second field. From the second drop down, select your find-collect-zipcode widget.

Screenshot showing transition configuration for find-collect-zipcode widget

Repeat this same process for the give-collect-zipcode and exit widgets, using your comma-separated list of potential responses for each of those options. When you’re done, save the split_1 widget. At this point, this part of the flow should look something like this:

screenshot showing branched widgets in a horizontal line beneath the split_1 widget

Continue with the find and give candy flows

Now you’ll work on the part of the flow that follows a user indicating they want to either find treat-or-treat locations or add their location to the list. This part of the flow will look like the following image:

Screenshot showing closeup of find and give candy flow

The users zip codes were already collected in the find-collect-zipcode and give-collect-zipcode you added above.

Now you’ll want to pass these zip codes to the associated Twilio functions you created earlier.

Below the find-collect-zipcode widget, leave a little space, and add a Run Function widget from the widget library. Call the new widget find-candy, and in the dropdowns select your function service, ui environment, and the name of the function you want to run, which should be /find-candy.

Screenshot of run function widget configuration panel with details about function that will be running

Then, keep scrolling down in the configuration panel to find the Function Parameters section. Click the Add button to add a new function parameter. In the key field, type “zipcode” and in the value field add {{widgets.find-collect-zipcode.inbound.Body}}.

screenshot of find candy widget configuration panel showing function parameter settings

Save the widget, and then connect it to the Reply option of the find-collect-zipcode widget.

The process for the give candy part of the flow will be the same, except you need to collect the user’s street address or cross streets before running the /give-candy function.

To do this, add an additional Save and Wait for Reply widget to below the give-collect-zipcode widget. Name it give-collect-address and set the message body to “What's your street address or nearest cross street? ex. 555 Oak St. or South Maple and Main”. Connect this widget to the Reply option of the give-collect-zipcode widget.

After saving the new widget, you can add the Run Function widget for /give-candy, being sure to add two function parameters:

  • key: zipcode,  value: {{widgets.give-collect-zipcode.inbound.Body}}
  • key: address,  value: {{widgets.give-collect-address.inbound.Body}}

Connect this widget to the Reply option of the give-collect-address widget.

Now, you could end the flow for these two interactions here. But to give the user a better experience, after running the functions, you could prompt them to see if they have any other tasks to complete.

Basically, at this point, you want the user to be able to start the entire flow over again. For example, what if after looking for trick-or-treat locations, the user also wants to list their location? With Studio, you can reuse all the widgets you’ve already created to keep the flow going. In this case, you’ll just need to prompt them and if they engage, collect their response to direct the chatbot, just like you did earlier.

Loop around to the beginning

Centered below the find-candy and give-candy widgets, add another Save and Wait for Reply widget with name loop-around, and message body “Do you have any other spooky tasks?”.

Connect the Success options for both the find-candy and give-candy widgets to this widget.

Below and to the left of the loop-around widget, add a Set Variables widget. Name this widget set-task-variable2, and in the configuration panel set the variable name to activity and the value to {{widgets.loop-around.inbound.Body}}. Connect this widget to the Reply option of the loop-around widget.

Screenshot showing set-task-variable2 widget below loop-around widget

Earlier in this tutorial, you saved the initial user response to a variable called activity. The value of the activity variable was used to split the flow of the chatbot. In this step, you allowed the user to override that variable with the user’s new requested activity.

Connect the Next option of the set-task-variable2 widget all the way back up to the split_1 widget. With this connection, your user can continue interacting with the chatbot for as long as they like.

Handle unclear responses

If the user sends a reply that doesn’t match the find candy, give candy, or exit conditions, then it’s considered an unclear response. You created the unclear-response widget earlier in this tutorial.

Because you don’t want the interaction to end if the user submits an unclear response (you want them to try again!), the unclear-response widget waits for a reply. In the same way you overrode the activity variable after collecting the loop-around response, you’ll override it again here with a new Set Variables widget.

Add this widget directly below the unclear-response widget:

Screenshot showing set-task-variable3 widget below unclear-response widget

Configure the widget’s name to be set-task-variable3, the variable name to be activity, and the variable value to be {{widgets.unclear-response.inbound.Body}}.

Connect the Next option of the set-task-variable3 widget to the split_1 widget.

Handle exits

The last step is to handle exits. Exits occur when a user asks to exit or when they never reply. Review all your widgets and connect any unconnected No Reply and Fail options to the exit widget.

If you want to expand your flow or improve the user experience, you can also create new widget connections to handle Fail and Delivery Fail options. You also might want to build in some checking to make sure that the user is entering correct zip codes and addresses.

After making the exit widget connections, be sure to save any active widgets, and then hit the Publish button at the top of the canvas. At this point your chatbot Studio flow is complete!

Connect your Twilio phone number to the chatbot

The last step is to connect your Twilio phone number to your Studio flow.

To do so, navigate to the Active Phone Numbers section of the Twilio Console. Find the phone number you intend to use for your chatbot, and click on it to open the number’s configuration page.

Scroll down to the Messaging section of the page.

screenshot showing messaging configuration section for the phone number in the twilio console

Under Configure with other handlers dropdown, be sure Webhooks, TwiML Bins, Functions, Studio, or Proxy is selected.

For the A Message Comes In drop down, select Studio Flow and in the second dropdown, select the name of your studio flow.

Then, click the Save button at the bottom of the page.

You’re all done!

Test your chatbot

Take your chatbot for a spin. Send any text message to the Twilio phone number you just configured.

Screenshot showing chatbot message thread from iphone

Screenshot showing more of conversation with chatbot


Next steps

I hope you had a great time building this chatbot and learned a lot about Twilio Studio and Twilio Functions. Complete your project by learning how to connect your chatbot to Airtable, or check out some of our other haunted articles, like this one on building a ghost-writing app with GPT-3, or this one on building a socially distant candy dispenser.

Happy hallowEeEeEeEeeeen!

Ashley is a JavaScript Editor for the Twilio blog. To work with her and bring your technical stories to Twilio, find her at @ahl389 on Twitter. If you can’t find her there, she’s probably on a patio somewhere having a cup of coffee (or glass of wine, depending on the time).