Shopify doesn't stop sending webhook to Flask app

Question:

I have a Flask app that listens to incoming Shopify webhooks, does something, and responds with 200. However, Shopify keeps sending the webhook as if my app responded with something other than 200.

When I simulate this with curl:

curl -H "Content-Type: application/json" -D /tmp/dump.log -X POST -d @<valid json> <my server>

the response headers are:

HTTP/1.1 100 Continue

HTTP/1.1 200 OK
Date: Thu, 16 Apr 2015 09:25:23 GMT
Server: Apache/2.4.7 (Ubuntu)
Content-Length: 0
Content-Type: text/html; charset=utf-8

I believe that the first 100 Continue is what’s making Shopify think my app failed. I get the same response on my local instance as well as on production.

What is going on and how can I fix it?

EDIT

Here’s the python code to process the request

import json
from flask_mail import Mail, Message
from pyPdf import PdfFileWriter, PdfFileReader
from flask import Flask
from certificate import Certificate
from flask import request
from purchase import Purchase
from stars import Stars

app = Flask(__name__)
app.config.from_envvar('NAMESTAR_SETTINGS')
app.config["MAIL_SERVER"] = 'smtp.sendgrid.net'
app.config["MAIL_PORT"] = 587
app.config["MAIL_USERNAME"] = app.config.get('EMAIL_LOGIN')
app.config["MAIL_PASSWORD"] = app.config.get('EMAIL_PASSWORD')
app.config["MAIL_DEFAULT_SENDER"] = app.config.get('SENDER')
app.config["PATH"] = app.config.get('PATH')
ADMINS = app.config.get('ADMINS')
mail = Mail(app)


def get_request():
    if request.args.get('hyg') is not None:
        return int(request.args.get('hyg'))
    else:
        return request.data


#returns an instance of Purchase class based on the request
def get_purchase():    
    pass


#creates and saves a PDF created with reportlab
def generate_certificate(purchase):
    pass


#sends the generated PDF to the recipient    
def send_email(certificate_path, recipient, name=""):
    msg = Message("Message", recipients=[recipient])
    with app.open_resource(certificate_path) as fp:
        msg.attach("certificate.pdf", "application/pdf", fp.read())
    f = open(app.config.get('PATH') + "/email_templates/certificate", "r")
    html = f.read()
    msg.html = html % (name)
    mail.send(msg)


@app.route('/', methods=['GET', 'POST'])
def generate():
    try:
        purchase = get_purchase()        
        certificate_path = generate_certificate(purchase)
        send_email(certificate_path, purchase.customer_email(), name=purchase.customer_name())        
    except Exception as e:        
        raise

    return ('', 200)


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

The server is Apache, here’s the virtual host settings for the app

<VirtualHost *:80>
    DocumentRoot /var/www/namestar/current   
    WSGIDaemonProcess namestar user=www-data group=www-data threads=5
    WSGIScriptAlias / /var/www/namestar/current/namestar.wsgi    
    <Directory /var/www/namestar/current>
        WSGIScriptReloading On
        WSGIProcessGroup namestar
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all
    </Directory>    
    SetEnv NAMESTAR_SETTINGS="/var/www/namestar/current/config/production.cfg"   
</VirtualHost>
Asked By: Michal Holub

||

Answers:

Shopify said that the requests were timing out. Indeed, the processing took between 10-20 seconds so it makes sense. I moved the bulk of the execution to it’s own thread:

threading.Thread(target=generate, args=[purchase]).start()

and all is working as expected.

Answered By: Michal Holub

I was facing the same issue. I think shopify tries the url again if the route doesnt complete the process in 5 secondes so my email was being sent twice.

For the newbies like myself and to complete Michael Holub answer, incorporating a new thread in my code looked like this and seemed to work:

from flask import Flask
import sys

from flask import request


from flask_mail import Mail, Message


app = Flask(__name__)


app.config.update(dict(
    DEBUG = True,
    MAIL_SERVER = 'smtp.gmail.com',
    MAIL_PORT = 587,
    MAIL_USE_TLS = True,
    MAIL_USE_SSL = False,
    MAIL_USERNAME = '****@gmail.com',
    MAIL_PASSWORD = '****',
))

mail = Mail(app)

@app.route('/', methods=['GET', 'POST'])
def welcome():
    return "Hello World!"

def get_purchase():   
    data = request.get_json()
    return data

def generate(purchase):
    with app.app_context():
        try:               
            msg = Message('Hello', sender = '***@gmail.com', recipients = ['****@gmail.com'])
            msg.body = str(purchase)
            mail.send(msg)
        except Exception as e:  
            print('error',  file=sys.stderr)      
            raise
    


@app.route('/hook', methods=[ 'POST'])
def hook():
    try:
        purchase = get_purchase()
        threading.Thread(target=generate, args=[str(purchase)]).start()
    except Exception as e:  
        print('error',  file=sys.stderr)      
        raise
    return ('', 200)



if __name__ == '__main__':
    
    app.debug = False
    app.run(debug=False)
Answered By: Jeremie Houet
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.