How to Receive Images on WhatsApp Using Flask and Twilio

June 30, 2021
Written by
Diane Phan
Twilion
Reviewed by

header - How to Receive Images on WhatsApp Using Flask and Twilio

The WhatsApp Business API from Twilio is a powerful, yet easy to use service that allows you to communicate with your users on the popular messaging app. In this tutorial you are going to learn how to create a Python application based on the Flask web framework that can receive and handle images sent by your users on WhatsApp.

Project demo

Prerequisites

To follow this tutorial you need the following items:

The Twilio WhatsApp sandbox

Twilio provides a WhatsApp sandbox, where you can easily develop and test your application. Once your application is complete you can request production access for your Twilio phone number, which requires approval by WhatsApp.

In this section you are going to connect your smartphone to the sandbox. From your Twilio Console, select Messaging, then click on “Try it Out”. Choose the WhatsApp section on the left hand sidebar. The WhatsApp sandbox page will show you the sandbox number assigned to your account, and a join code.

WhatsApp Sandbox configuration

To enable the WhatsApp sandbox for your smartphone, send a WhatsApp message with the given code to the number assigned to your account. The code is going to begin with the word "join", followed by a randomly generated two-word phrase. Shortly after you send the message you should receive a reply from Twilio indicating that your mobile number is connected to the sandbox and can start sending and receiving messages.

If you intend to test your application with additional smartphones, then you must repeat the sandbox registration process with each of them.

Project setup

In this section you are going to set up a brand new Flask project. To keep things nicely organized, open a terminal or command prompt, find a suitable place and create a new directory where the project you are about to create will live:

mkdir receive-whatsapp-img-flask
cd receive-whatsapp-img-flask

Create a virtual environment

Following Python best practices, you are going to create a virtual environment to install the Python dependencies needed for this project.

If you are using a Unix or Mac OS system, open a terminal and enter the following commands to create and activate your virtual environment:

python3 -m venv venv
source venv/bin/activate

If you are following the tutorial on Windows, enter the following commands in a command prompt window:

python -m venv venv
venv\Scripts\activate

Now you are ready to install the Python dependencies used by this project:

pip install flask twilio pyngrok python-dotenv

The four Python packages that are needed by this project are:

  • The Flask framework, to create the web application that will receive message notifications from Twilio.
  • The Twilio Python Helper library, to work with WhatsApp messages.
  • Pyngrok, to make the Flask application temporarily accessible on the Internet for testing via the ngrok utility.
  • The python-dotenv package, to read a configuration file.

Set up a development Flask server

Make sure that you are currently in the virtual environment of your project’s directory in the terminal or command prompt. Since we will be utilizing Flask throughout the project, we will need to set up the development server. Add a .flaskenv file (make sure you have the leading dot) to your project with the following lines:

FLASK_APP=app.py
FLASK_ENV=development

These incredibly helpful lines will save you time when it comes to testing and debugging        your project.

  • FLASK_APP tells the Flask framework where our application is located.
  • FLASK_ENV configures Flask to run in debug mode.

Run the command flask run in your terminal to start the Flask framework.

terminal showing the output of "flask run" command. flask is running with environment on development

The screenshot above displays what your console will look like after running the command flask run. The service is running privately on your computer’s port 5000 and will wait for incoming connections there. You will also notice that debugging mode is active. When in this mode, the Flask server will automatically restart to incorporate any further changes you make to the source code.

However, since you don't have an app.py file yet, nothing will happen. Though, this is a great indicator that everything is installed properly.

Feel free to have Flask running in the background as you explore the code. We will be testing the entire project at the end.

Start an ngrok tunnel

The problem with the Flask web server is that it is local, which means that it cannot be accessed over the Internet. Twilio needs to send web requests to this server, so during development, a trick is necessary to make the local server available on the Internet.

On a second terminal window, activate the virtual environment and then run the following command:

ngrok http 5000

The ngrok screen should look as follows:

ngrok

While ngrok is running, you can access the application from anywhere in the world using the temporary forwarding URL shown in the output of the command. All web requests that arrive into the ngrok URL will be forwarded to the Flask application by ngrok.

Create a WhatsApp webhook

Twilio uses the concept of webhooks to enable your application to perform custom actions as a result of external events such as receiving a message from a user on WhatsApp. A webhook is nothing more than an HTTP endpoint that Twilio invokes with information about the event. The response returned to Twilio provides instructions on how to handle the event.

The webhook for an incoming WhatsApp message will include information such as the phone number of the user and the text of the message. In the response, the application can provide a response to send back to the user. The actions that you want Twilio to take in response to an incoming event have to be given in a custom language defined by Twilio that is based on XML and is called TwiML.

Receive and respond to WhatsApp messages

Create a file named app.py and paste the following code so that the endpoint logic can extract the information about the incoming WhatsApp message:

import os
from dotenv import load_dotenv
from flask import Flask, request
from twilio.twiml.messaging_response import MessagingResponse

load_dotenv()

app = Flask(__name__)

def respond(message):
    response = MessagingResponse()
    response.message(message)
    return str(response)

@app.route('/message', methods=['POST'])
def reply():
    sender = request.form.get('From')
    message = request.form.get('Body')
    media_url = request.form.get('MediaUrl0') 
    print(f'{sender} sent {message}')
    if media_url:
        return respond('Thank you! Your image was received.')
    else:
        return respond(f'Please send an image!')

When a WhatsApp message is sent to the sandbox, the /message route checks if that message exists. Then, the endpoint uses the MessagingResponse class from the twilio package to build the response that goes back to the user. The respond() method of this class instructs Twilio to respond to the user with the message given as an argument.

Twilio will invoke the webhook as a POST request, and it will pass information about the message as form variables, which can be retrieved in Flask from the request.form dictionary. In particular, the phone number of the user, the text of the message, and an image URL (when an image is included in the message) are useful for this project. These can be obtained from the From, Body and MediaUrl0 form fields respectively.

This /message endpoint above extracts these three values and then prints the phone number and the message to the console. The endpoint concludes by sending a response to the user, which varies depending on the user having sent an image or not.

Save the image

The next step is to download the image URL that is stored in the media_url variable, and save it locally. Before you do that, create a dedicated uploads directory where all these images will be stored. Run the following command in the root directory of the project:

mkdir uploads

Inside the uploads directory, the application will create a dedicated sub-directory for each user, which will be identified by their WhatsApp phone number. The images uploaded by a given user will be stored all together in this sub-directory.

Below you can see the updated app.py:

import os
import requests    # ← new import
from dotenv import load_dotenv
from flask import Flask, request
from twilio.twiml.messaging_response import MessagingResponse

load_dotenv()

app = Flask(__name__)

def respond(message):
    response = MessagingResponse()
    response.message(message)
    return str(response)

@app.route('/message', methods=['POST'])
def reply():
    sender = request.form.get('From')
    message = request.form.get('Body')
    media_url = request.form.get('MediaUrl0')
    print(f'{sender} sent {message}')
    if media_url:
        r = requests.get(media_url)
        content_type = r.headers['Content-Type']
        username = sender.split(':')[1]  # remove the whatsapp: prefix from the number
        if content_type == 'image/jpeg':
            filename = f'uploads/{username}/{message}.jpg'
        elif content_type == 'image/png':
            filename = f'uploads/{username}/{message}.png'
        elif content_type == 'image/gif':
            filename = f'uploads/{username}/{message}.gif'
        else:
            filename = None
        if filename:
            if not os.path.exists(f'uploads/{username}'):
                os.mkdir(f'uploads/{username}')
            with open(filename, 'wb') as f:
                f.write(r.content)
            return respond('Thank you! Your image was received.')
        else:
            return respond('The file that you submitted is not a supported image type.')
    else:
        return respond('Please send an image!')

The media_url variable is going to be set only if the user sent a message that included a file, so all the logic that handles and saves images is skipped if media_url is set to None.

The file can be downloaded using the requests package, just by sending a GET request to the image URL. Unfortunately the URL as sent by Twilio does not include a file extension, so the only way to determine if the file that was submitted by the user is an image is to look at the Content-Type header of the response. The code above only accepts JPEG, PNG and GIF images.

When the content type is recognized, the filename variable is set with the location where the file is going to be saved, which is a sub-directory named with the user’s phone number inside the uploads folder created above. For the name of the image file we’ll use the message text, and the extension is determined by the content type.

Before saving the file, the code checks if the sub-directory with the user’s phone number exists, and if it doesn’t it creates it. Then the content of the image, which can be obtained from the response object returned by requests, is written to the file.

To complete the webhook you will send a response back to the user through WhatsApp to let them know that their image has been received (or an error message if they sent an invalid file).

Configure the WhatsApp webhook

To connect the webhook with WhatsApp you need to configure its URL in the Twilio console. Locate the WhatsApp sandbox settings page and edit the “When a message comes in” field with the URL of your webhook. This is going to be the temporary ngrok URL with /message appended at the end. You can see an example below:

WhatsApp webhook configuration

Make sure the dropdown to the right of the URL field is set to “HTTP Post”, and don’t forget to click the “Save” button at the bottom of the page to record these changes.

Test your WhatsApp messaging service

And now the moment of truth! Make sure the Flask server and ngrok are still running. From your smartphone, send a message along with an image to the sandbox number. After a short moment you should see a response.

Project demo

Next steps

I hope you found this tutorial useful. Now that you learned how to receive messages with images on WhatsApp, you may want to take on a slightly more challenging project. If that’s the case, head over to the build a WhatsApp chatbot tutorial.

I’d love to see what you build with WhatsApp and Twilio!

Diane Phan is a developer on the Developer Voices team. She loves to help programmers tackle difficult challenges that might prevent them from bringing their projects to life. She can be reached at dphan [at] twilio.com or LinkedIn.