0

Similar to a previous SO query, I have a webhook receiver that listens for a POST. I send a WhatsApp message to my Twilio number, Twilio POSTs to the webhook receiver, the server code processes the request and then returns a response object to Twilio. The code pulls some items from an events calendar on Airtable and should send them to WhatsApp via Twilio.

The webhook tests OK with Postman. However, WhatsApp doesn't return results of the POST request and I receive the following warning from the Twilio Debugger:

sourceComponent     "14100"
line                "1"
ErrorCode           "12200"
LogLevel            "WARN"
Msg                 "Content is not allowed in prolog."
EmailNotification   "false"
parserMessage       "Content is not allowed in prolog."
cols                "1"

My full code is here:

from flask import Flask, request
import requests
import json
from airtable import Airtable
from datetime import datetime
from twilio.twiml.messaging_response import MessagingResponse

base_key = 'BASE_KEY'
table_name = 'Events Calendar'
api_key = 'API_KEY'
airtable = Airtable(base_key, table_name, api_key)

API_URL = "https://api.airtable.com/v0/BASE_KEY/Events%20Calendar?maxRecords=3&filterByFormula=IS_AFTER(%7BDate%7D,NOW())"

headers = {
    "Authorization": "Bearer API_KEY"
}

app = Flask(__name__)

@app.route('/bot', methods=['POST'])
def accessdb():
    incoming_msg = request.values.get('Body', '').lower()
    resp = MessagingResponse()
    msg = resp.message()
    responded = False
    
    if 'next' in incoming_msg:
        def pretty(d):
            date = datetime.strptime(d["Date"], "%Y-%m-%dT%H:%M:%S.%fZ")
            return f'''Date: {date.strftime("%A, %d. %B %Y %I:%M%p")}
Title: {d['Title']}
Description: {d['Description']}
'''
        pages = airtable.get_iter(maxRecords=3, formula="Date >= NOW()", sort=["Date"], fields=('Date', 'Title', 'Description'))
        mylist = [] 
        
        for page in pages:
            for record in page:
                if 'fields' not in record:
                    continue
                fields = record['fields']
                mylist.append(pretty(fields))
        return "\n".join(mylist)

    elif 'what' in incoming_msg:
        msg.body("Please enter 'next' to get the next events.")
        responded = True

    elif not responded:
        msg.body("I don't know about that, sorry!")
    return str(resp)

if __name__ == '__main__':
    app.run()

The above code is deployed to Heroku and I can use curl or Postman to call the webhook and successfully yield the following results:

Date: Wednesday, 07. October 2020 06:00PM
Title: Social Distancing Dance Party: Slow Motion
Description: Slow Motion is the yang to the Saturday Dance Party's yin

Date: Thursday, 08. October 2020 08:00AM
Title: Free Online Meditation Mornings
Description: 20-30 minute meditation. Donations can be made at https://example.com

Date: Saturday, 10. October 2020 07:00PM
Title: Social Distancing Dance Party
Description: A party in your very own kitchen

But calling the webhook from Twilio produces the error given previously.

The earlier SO answer advises to return the query results as follows: return twilioResponse.ToString()

I'm not confident about the syntax and have tried the following:

        for page in pages:
            for record in page:
                if 'fields' not in record:
                    continue
                fields = record['fields']
                mylist.append(pretty(fields))
        return "\n".join.twilioResponse.ToString(mylist)

However when I try the above it fails as follows:

    return "\n".join.twilioResponse.ToString(mylist)
AttributeError: 'builtin_function_or_method' object has no attribute 'twilioResponse'

A nudge in the right direction would be much appreciated. Many thanks in advance.

2
  • Can you share a bit more of your code? It is not clear where you are getting some of the variables from or where you return the webhook response. Commented Oct 6, 2020 at 6:04
  • Thank you @philnash - I've added more info. Much appreciated. Commented Oct 6, 2020 at 22:00

1 Answer 1

0

Twilio developer evangelist here.

I think the issue here is that when you send the bot "next" it is responding with a string of results and not TwiML, which is what Twilio is expecting. You are returning TwiML if the user sends "what" or anything else though, so there's not a lot to change.

You need to take this code:

        for page in pages:
            for record in page:
                if 'fields' not in record:
                    continue
                fields = record['fields']
                mylist.append(pretty(fields))
        return "\n".join(mylist)

And rather than return right at the end, set the string you've built to be the message in the TwiML response, like this:

        for page in pages:
            for record in page:
                if 'fields' not in record:
                    continue
                fields = record['fields']
                mylist.append(pretty(fields))
        msg.body("\n".join(mylist))

Now you are setting the list of events as the message to send and it will be returned to Twilio as TwiML by the line return str(resp) at the end of your method.

As a side point, I don't believe you need to use the responded boolean throughout your conditional. Instead you can just complete your conditional with an else which will catch any cases you haven't matched before. Like this:

    else:
        msg.body("I don't know about that, sorry!")

Let me know if that helps at all.

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you @philnash - yes, that works. Amazing. I'll review the code with regard to the responded boolean - I had adapted the Flask/Twilio tutorial from here. This has been such a journey and learning experience. Thank you again.
A (hopefully) quick question @philnash, whilst I have your attention - WhatsApp 'helpfully' expands any URLs in the message body. Is there a way to suppress that behaviour as it messes with the formatting of the message?
You may be able to surround the URL with backticks to have it render as code, which might suppress the URL unfolding. Though I haven’t tried this, so I can’t promise it works. See this article for details on text formatting in WhatsApp.
Many thanks @philnash - I'll take a look at that. All the best.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.