Create Your First Real Estate Lead-Capturing Chatbot Using Voiceflow and Python Flask
Tags: python, chatbots
Published: 2024-03-04 07:49:32

---
title: Create Your First Real Estate Lead-Capturing Chatbot Using Voiceflow and Python Flask
published: true
date: 2024-01-07 20:58:34 UTC
tags: python,chatbots
canonical_url: https://itnext.io/create-your-first-real-estate-lead-capturing-chatbot-using-voiceflow-and-python-flask-7ec004866927
---

![](https://cdn-images-1.medium.com/max/1024/0*ELoH54OOssAJrjiJ.png)

### Introduction

Chatbots are essential tools because they make communication between people and technology much easier. These clever programs can answer questions, provide helpful information, and even assist with tasks. In various fields like real estate, chatbots play a crucial role by engaging with potential clients, enhancing user experience, and helping businesses capture valuable leads. They’re like smart assistants who work around the clock, making interactions smoother and more efficient for everyone involved.

Chatbots come in different levels of complexity, categorized as either stateless or stateful. Stateless chatbots treat each conversation as if it’s a new interaction with a user, while stateful chatbots can remember and consider past exchanges when generating responses.

Integrating a chatbot into a service or sales department is often a low or no-code endeavor. Numerous chatbot service providers offer tools that empower developers to create conversational user interfaces seamlessly integrated into third-party business applications.

In this post, I am going to develop a real estate chatbot that will display properties from the website and then capture all information about the potential lead. I am using the [OnTheMarket](https://www.onthemarket.com/) website for the sake of the demo. I am using the [VoiceFlow chatbot platform](https://voiceflow.com/) which is very yet very powerful. Unlike many others, VoiceFlow provides a trial account for 2 weeks, enough to test your idea. I will be using unofficial APIs of OnTheMarket in Python Flask. There will also be a web interface to display captured leads. If you are in a hurry and want to see how it works, watch the video below:

### Introduction to VoiceFlow

[VoiceFlow](https://www.voiceflow.com/) is a chatbot platform that facilitates you to create and deploy your chatbot whether they are AI-based or non-AI. It provides an easy-to-use designer to create the flow of your bot and a Javascript code snippet to deploy your bot on a website. The bot could be integrated with other platforms as well for instance WhatsApp, Instagram, etc with the help of 3rd party tools. It provides a 2-week trial which is enough to test things, plus they also offer a Sandbox mode. Visit their [pricing](https://www.voiceflow.com/pricing) page for further details.

### Bot Development

OK! So we are going to make a chatbot for [OnTheMarket(OTM)](https://www.onthemarket.com/), one of the famous property portals for the UK. The flow of the bot is simple; it will ask whether you want to buy a house or for rent, minimum/maximum prices, location, and the number of bedrooms, and return a list of properties within the chatbot. The user will select the properties he is interested in and then his contact details along with the preferences will be stored in the database. That’s it.

### Rest API for properties

First and foremost, we need some way to send user preferences to the OTM website and retrieve the relevant listings. I am creating a simple endpoint in Flask that will access the _unofficial_ OnTheMarket API to retrieve data.

```
def search_properties(search_type, location_id, min_price, max_price, min_bedrooms, property_type):
    property_types = [
        'semi-detached',
        'terraced',
        'bungalows',
        'flats-apartments',
    ]
    search_types = [
        'to-rent',
        'for-sale',
    ]

    headers = {
        'authority': 'www.onthemarket.com',
        'accept': 'application/json',
        'accept-language': 'en-US,en;q=0.9,ur;q=0.8,zh-CN;q=0.7,zh;q=0.6',
        'cache-control': 'no-cache',
        'content-type': 'application/json; charset=utf-8',
        'dnt': '1',
        'pragma': 'no-cache',
        'referer': 'https://www.onthemarket.com/to-rent/property/london/?max-price=1250&min-bedrooms=1&min-price=100',
        'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"macOS"',
        'sec-fetch-dest': 'empty',
        'sec-fetch-mode': 'cors',
        'sec-fetch-site': 'same-origin',
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    }
    params = {
        'search-type': search_type,
        'location-id': location_id,
        'min-price': min_price,
        'max-price': max_price,
        'min-bedrooms': min_bedrooms,
        'prop-types': property_types,
    }
    r = requests.get('https://www.onthemarket.com/async/search/properties/', params=params, headers=headers)
    r.raise_for_status()
    data = r.json()['properties']
    return data
```

The above function accesses the OTM’s endpoint and returns data in JSON format.

```
@app.route('/find', methods=['POST'])
def find_properties():
    refined_properties = []
    return_msg = {}
    try:
        form_data = request.form.to_dict()

        properties = search_properties(
            search_type=form_data['search-type'],
            location_id=form_data['location-id'],
            min_price=int(form_data['min-price']),
            max_price=int(form_data['max-price']),
            min_bedrooms=int(form_data['min-bedrooms']),
            property_type=form_data['prop-type']
        )

        for property in properties:
            if 'display_address' in property:
                address = property['display_address']
                price = property['price']
                property_link = property['property-link']
                image = property['cover-images']['default']
                refined_properties.append(
                    {'title': address,
                     'price': price,
                     'url': 'https://www.onthemarket.com{}'.format(property_link),
                     'image': image,
                     })
        return_msg = {'status': 'OK', 'msg': refined_properties}
    except Exception as ex:
        return_msg = {'status': 'FAIL', 'msg': str(ex)}
    finally:
        return jsonify(data=return_msg)
```

Then, I iterate the records and just extract the required data points. You could connect OTM’s API directly, up to you. I found this way much better and cleaner. Anyways, our API is ready but you can’t connect it with VoiceFlow because it is running locally. So what’s the solution? Well, use [ngrok](https://ngrok.com/download) for HTTP tunneling. After running the Flask app, I will just initiate _ngrok_ by using ngrok http 5000. Make sure your Flask app is already running. If all goes well you will see something like the following:

![](https://cdn-images-1.medium.com/max/300/0*LFDMRct0Vh7OU0BE.png)

Let’s check whether the API is working, head on to Postman, and test the POST request:

![](https://cdn-images-1.medium.com/max/300/0*sGU7Amx0q7jUKP9F.png)

As you can see, it returned the required response

### VoiceFlow Designer

#### Creating Project and Adding a Greeting Node

Alright, now it’s time to create our first chatbot. Assuming you are successfully registered, visit [https://creator.voiceflow.com/](https://creator.voiceflow.com/) and hit the **New Assistant** button. Once you do it, you will see a dialog box like the one below. Enter your bot details.

![](https://cdn-images-1.medium.com/max/274/0*-ZhArHIec6TL8H3n.png)

When you open the designer first time it will appear like the below, select all and hit the delete button to clear the entire canvas.

![](https://cdn-images-1.medium.com/max/300/0*X_SSvUkR2ou9GqZs.png)

![](https://cdn-images-1.medium.com/max/300/0*wS6Blt3iYR5ytPck.png)

The very first thing I am going to do is to add a welcome message and then run the bot.

![](https://cdn-images-1.medium.com/max/600/0*aVVEtdNMtFt_zFgN.gif)

I added a _Text Node_ for greetings and then gave it a test run after connecting the start node. There will always be an undeletable _start_ node. Cool, isn’t it?

Now, before I move further, I want to let the bot know about the URL of my API endpoint. Since I prefer a configurable/modularized code, I will assign the base URI to a variable.

#### Variables

VoiceFlow has a concept of _variables_. If you want to work on VoiceFlow you must be well equipped with this concept. Variables in VoiceFlow can hold values that are available in other steps. There are multiple ways you can create a variable in VoiceFlow. The easiest way is to go to the section(Press _M_) which opens a dialogue box like the one below:

![](https://cdn-images-1.medium.com/max/300/0*E4jEmu0xMw2qoNaT.png)

What you see above are default or system variables offered by VoiceFlow. Let’s add a variable BASE\_URI

![](https://cdn-images-1.medium.com/max/600/0*T2CCVm2LlzwVOTnj.gif)

The variable has been created but not initialized! You can either use the **_Set_** step to assign a variable or you can do it in JavaScript. Yes… VoiceFlow can manipulate things in JavaScript as well!

![](https://cdn-images-1.medium.com/max/302/0*4pdj5K1bTzndZkwN.gif)

Do note that I have added the URL within double quotes. VoiceFlow expects all strings within double quotes otherwise it does not set at all. VoiceFlow also allows you to debug variables which helps you understand how things are working. For clarity purposes, I am giving a static image of the debugging section.

![](https://cdn-images-1.medium.com/max/146/0*Szs978FBStNRnJv6.png)

As you can see, BASE\_URI is set to _ngrok_ URL. The last\_response variable contains the value returned by the chatbot. Though last\_utterance is empty here, its value is set when a user enters his response. You can also use markdown syntax to format text, like the below:

![](https://cdn-images-1.medium.com/max/300/0*zJhOJlR4mNhz1PYC.png)

And it will appear like the below:

![](https://cdn-images-1.medium.com/max/200/0*hE6VvK5JbGgjJ4Rp.png)

Now, let’s add buttons for _Buy_ and _Rent_

![](https://cdn-images-1.medium.com/max/302/0*ns1BWpcHMPL99jcL.gif)

A couple of things are happening here: We are adding two buttons and then assigning a new variable search\_type and then assigning values to it. These values will help to make the API call figure out whether someone wants to buy or seeking a property for rent. This is how buttons are rendered:

![](https://cdn-images-1.medium.com/max/217/0*0PXrGmieaMo6t6IY.png)

And this is how they are rendered:

![](https://cdn-images-1.medium.com/max/1024/0*QY4hrCWNOK6fN__4.png)

Notice the search\_type value now set to _for-sale_

#### JavaScript and Buttons

Now I want to let the user know what he opted for. One simple way is to just display the value behind the search\_type value but it does not look good if you are setting non-human-friendly values. The best option is to display a value based on what he selected. For that purpose, we will be using the **_JavaScript_** step.

![](https://cdn-images-1.medium.com/max/640/0*ySqShGK0uvDt29Kg.gif)

As you can see we introduced a new variable search\_type\_text within JavaScript and then created in the VoiceFlow system in the _Text_ step. You could create the variable by going into the **_Variables_** section too. In either case, the variable must be created in VoiceFlow. The JavaScript step can not create a new VoiceFlow variable. Once it is created, you can display the formatted text in the bot. Next, we will select the desired location and store the selected value in location variable

![](https://cdn-images-1.medium.com/max/300/0*0AgzF8UpRBDTHO9Z.png)

Whatever location is selected by the user, the info is stored in location a variable. Now we will be using the **_JavaScript_** step again to format the selected location for the display.

![](https://cdn-images-1.medium.com/max/300/0*EQL8dyRUmHrFRbLs.png)

BTW, you can assign a certain color to a block or set of blocks. For instance, I assigned a pinkish color to all steps that display variable values.

![](https://cdn-images-1.medium.com/max/300/0*p6paWR_KCZDkfpGv.png)

#### Capture/Entities

Now we need to fetch the user’s preferences for minimum, maximum price, and number of bedrooms. I will show for minimum price only because it is the same for others.

Here I introduce the concept of **_Entities_**. Besides variables, VoiceFlow gives you the option to restrict the user input against a certain format. Here I am creating an entity min\_price of the type Number . You can define your format as well but for our requirement, the number format is enough. It also supports, Name, Phone, Email, and a few other formats as well. Similarly, we will capture the value of max\_price and no\_of\_bedrooms

The **_Capture_** step has also an option of **_No Match_** which gets activated if the required data format is not found.

Notice it asks for training when running the bot. It is because I added a new entity here. If things go wrong, it should ask to enter the minimum price again. One way is to connect the **Invalid max price** block by connecting with the arrow back to the **Select Minimum** block, another option is to use the _Action_ option to connect the blog which makes it a cleaner approach.

#### Condition

VoiceFlow also provides you the option of adding a _condition,_ **IF/ELSE** kind of thing_._ Adding a condition is pretty easy. All you have to do is to add a step.

![](https://cdn-images-1.medium.com/max/300/0*DKx8xXDvSm0e6k2D.png)

#### API

VoiceFlow gives you the facility to interact with 3rd party tools with the help of the **_API_** step. As the name suggests, this step is responsible for making an HTTP call and fetching the response for further action. In our case, it is to make the call to the API endpoint I introduced above.

As you can see, after passing the required parameters it returned a JSON payload. Notice the returned data is changed if you change the searching criteria, sweet. Now the next step is to display the data.

#### Carousel

Carousel is one of the visual elements offered by VoiceFlow to show multiple items. This is how a carousel appears in the chatbot window:

![](https://cdn-images-1.medium.com/max/234/0*sdFbkbLHLXgJi01F.png)

But I want something else. I want to generate these carousel items dynamically and for that, we will be using [Custom Actions](https://developer.voiceflow.com/docs/custom-actions-1).

#### Custom Actions

Custom Actions allow you to integrate 3rd party tools within VoiceFlow. It also allows you to render different VoiceFlow elements in JavaScript. First, you will create a custom action and you will name it a **_carousel_** and then you will add the following code in the **_JavaScript_** step

```
selected_properties = []

carousel_dynamic = {
  layout:"Carousel",
  cards : []
}

function createCard(title, image, description) {
  let cardContent = {
    "title": title,
    "description": {
      "text": description
    },
    "imageUrl": image,
    "buttons": [
      {
        "name": "I am interested",
        "request": {
          "type": "property_selected",
          "payload": {
            title
          }
        }
      }
    ]
  };
  return cardContent;
}

for(let i=0;i< searched_properties.length;i++) {
  let GBPFormatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'GBP',
  minimumFractionDigits: 0,
  maximumFractionDigits: 0
});

  carousel_dynamic.cards.push(createCard(searched_properties[i].title, searched_properties[i].image,searched_properties[i].price))  
}
// carousel_dynamic.cards.push(card,card)
// carousel_dynamic.cards.push(createCard('title', 'https://webbjenkins.imgix.net/PRA11696/images/apcc_PRA11696_IMG_00.jpg', 'description'))

carousel_dynamic = JSON.stringify(carousel_dynamic)
```

Well, this is not _exactly_ my code. I just refined the code given in one of the videos of VoiceFlow. Now, if you run this, this is how it would look like:

![](https://cdn-images-1.medium.com/max/720/1*HXqlFr5c5pOOXhNnYzA-Qg.gif)

Here, it is generating carousel items at runtime with the help of **_JavaScript_** and the **_Custom Action_**  step.

Now the next step is to capture contact details. It is the same as capturing min/maximum price and bedrooms. The captured data is sent via a POST call to the backend API and saved in a CSV which then is displayed on a page.

![](https://cdn-images-1.medium.com/max/300/0*cq3GaEYx-HhdtraW.png)

Cool! You can replace CSV with MySQL or any other DB, up to you. You can also shoot an email to both the potential client and the site Admin. You can also add the email to a newsletter so that you can send details of the new properties. There are endless possibilities. Now let’s give it a run from the start!

![](https://cdn-images-1.medium.com/max/720/1*uAzxbw9gIxFYNwL0BK-quA.gif)

#### Web Integration

Now the bot is ready, it’s time to integrate it into your website. Click on the **_Integration_** option(Press **5** ) on the Designer. When you open it you find 3 sections: **Installation** , **General** , and **Appearance**. The first thing I am going to do is to install the widget on a web page but before you even do that, you have to publish your bot.

![](https://cdn-images-1.medium.com/max/300/0*Av8euRg1arbqU7rQ.png)

So head on to the D **_esigner_** option(Press **1** ) and hit the **_Publish_** button. When you do it, it asks to version the bot which is optional.

![](https://cdn-images-1.medium.com/max/300/0*qkD3aZ5VDPYkvW5B.png)

After a successful publish it asks to embed the widget which leads to the integration page I shown above. So I followed the instructions and put the chat widget on the page. I created a new endpoint, called Demo and put the code in it.

![](https://cdn-images-1.medium.com/max/300/0*9LGRnhj00mEwp4Pa.png)

and here is the demo page endpoint:

```
@app.route('/demo', methods=['GET'])
def demo():
    return render_template('demo.html')
```

When I access this page it shows the chat window like the one below:

![](https://cdn-images-1.medium.com/max/300/0*FODEkYlbT5VpJ_8r.png)

But…it does not look so good. How about adding some branding-related stuff and customizing it? First I will change the message: **Starting Template — Webchat** in the **_General _** section.

![](https://cdn-images-1.medium.com/max/300/0*wtKfnvf1V3VFNQ63.png)

It will instantly reflect on the chat widget as soon as I do it. You can enable _Powered By_ Option if you want. After refreshing the page, the chat window looks like the below:

![](https://cdn-images-1.medium.com/max/201/0*C504EtZW_dmoRhgQ.png)

But it is not enough. I would like to change the color theme and icons. For that, I will open the **_Appearance_** section. After making changes, our bot will look like the below:

![](https://cdn-images-1.medium.com/max/210/0*Vv7rH7Z8h-asiO0z.png)

Does it not look good? I matched the _OnTheMarket_ website color theme and used its icon here. Want to see this chatbot in action? Well, watch the demo video I shared above. One thing! **_You must publish every time you make changes in the bot designer otherwise it will keep showing the old version._** Visit [here](https://learn.voiceflow.com/hc/en-us/articles/10569376208781) to learn more about it.

### Conclusion

In this post, I gave you a beginner-level comprehensive overview of the VoiceFlow platform and how you can create a chatbot by leveraging it. It is a basic-level bot. You can customize it by adding more checks and validation or integrate some 3rd party CRM into it. So that’s it, the end of a very long post. Like always, the code along with the VoiceFlow . **_vf_** file is available on [GitHub](https://github.com/kadnan/VoiceFlowLeadCapturingChatBot)

_Originally published at_ [_https://blog.adnansiddiqi.me_](https://blog.adnansiddiqi.me/create-your-first-real-estate-lead-capturing-chatbot-using-voiceflow-and-python-flask/) _on January 7, 2024._

* * *