python-3.xwebhookstwitterapi-python

what is the procedure of registering the webhook for twitter?


I am trying to register url as webhook on twitter through the curl command given in the twitter documentation. I think twitter documentation is a bit outdated or incorrect.

 curl --request POST --url "https://api.twitter.com/1.1/account_activity/all/prod/webhooks.json?url=https%3A%2F%2FXXXX.com%2Fwebhook%2Ftwitter" --header "authorization: OAuth oauth_consumer_key='XXXXXXXXXXXXXXXXXXXXXX', oauth_nonce='ODgyNjc5NjQ0MTM3NzI4NTcwMjY4NDQ0', oauth_signature='7daMyzB1JClE4xv8hXNCimWpGtA%3D', oauth_signature_method='HMAC-SHA1', oauth_timestamp='1568620293', oauth_token='XXXXXXXXXXXXXXXXXXXXX', oauth_version='1.0'"

after running this code, I am getting error as

{"errors":[{"code":215,"message":"Bad Authentication data."}]}

the codes that i have used to generate timestamp, oauth_nonce, oauth_signature is given below. These codes are in python3. I am not very sure whether they are giving the correct output or not.

oauth_timestamp = str(int(time.time()))


#oauth_nonce
def get_nonce():
    nonce = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits)for x in range(32))
return nonce


#oauth_signature

def escape(s):
"""Percent Encode the passed in string"""
    return urllib.parse.quote_plus(s, safe='~')


def create_auth_header(parameters):
"""For all collected parameters, order them and create auth header"""
    ordered_parameters = {}
    ordered_parameters =  collections.OrderedDict(sorted(parameters.items()))
    auth_header = (
    '%s="%s"' % (k, v) for k, v in ordered_parameters.items())
    val = "OAuth " + ', '.join(auth_header)
    return val


def generate_signature(method, url, url_parameters, oauth_parameters, oauth_consumer_key, oauth_consumer_secret, oauth_token_secret=None, status=None):
    """Create the signature base string"""

    #Combine parameters into one hash
    temp = collect_parameters(oauth_parameters, status, url_parameters)

    #Create string of combined url and oauth parameters
    parameter_string = stringify_parameters(temp)

    #Create your Signature Base String
    signature_base_string = (
    method.upper() + '&' +
    escape(str(url)) + '&' +
    escape(parameter_string)
    )

    #Get the signing key
    signing_key = create_signing_key(oauth_consumer_secret, oauth_token_secret)

    return calculate_signature(signing_key, signature_base_string)



 def stringify_parameters(parameters):
     """Orders parameters, and generates string representation of parameters"""
     output = ''
     ordered_parameters = {}
     ordered_parameters =  collections.OrderedDict(sorted(parameters.items()))

     counter = 1
     for k, v in ordered_parameters.items():
          output += escape(str(k)) + '=' + escape(str(v))
          if counter < len(ordered_parameters):
              output += '&'
              counter += 1

     return output



def collect_parameters(oauth_parameters, status, url_parameters):
    """Combines oauth, url and status parameters"""
    #Add the oauth_parameters to temp hash
    temp = oauth_parameters.copy()

    #Add the status, if passed in.  Used for posting a new tweet
    if status is not None:
        temp['status'] = status

    #Add the url_parameters to the temp hash
    for k, v in url_parameters.items():
        temp[k] = v

    return temp




def calculate_signature(signing_key, signature_base_string):
    """Calculate the signature using SHA1"""
    hashed = hmac.new(signing_key,    signature_base_string.encode('utf-8'), hashlib.sha1)


    sig = binascii.b2a_base64(hashed.digest())[:-1]

    return escape(sig)


def create_signing_key(oauth_consumer_secret, oauth_token_secret):
    """Create key to sign request with"""
    signing_key = escape(oauth_consumer_secret) + '&'

    signing_key += escape(oauth_token_secret)

    return signing_key.encode('utf-8')




 oauth_parameters = {
    'oauth_timestamp': str(int(time.time())),
    'oauth_signature_method': "HMAC-SHA1",
    'oauth_version': "1.0",
    'oauth_token': "vvvvvvvvvvvvvvv",
    'oauth_nonce': get_nonce(),
    'oauth_consumer_key': 'bbbbbbbbbbbbbbb'
     }


 oauth_parameters['oauth_signature'] = generate_signature(
    method,
    url,
    url_parameters, oauth_parameters,
    consumer_key,
    consumer_secret,
    access_token_secret
     )


 auth_headers = {'Authorization': create_auth_header(oauth_parameters),
            'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
                 }


 auth_data = {
       'grant_type': 'client_credentials'
  }


 webhook_endpoint ='https%3A%2F%2Fmy-site.com%2Fwebhook%2Ftwitter'

 url = 'https://api.twitter.com/1.1/account_activity/all/env/webhooks.json?url={}'.format(webhook_endpoint)

 method = "post"
 url_parameters = {
    'exclude_replies': 'true'
     }

 r = requests.post(url, headers=auth_headers, data=auth_data)

Kindly tell me which code is incorrect or am i doing something wrong? If you can write the code below in the answer, it would be beneficial.


Solution

  • Ok, so i was finally able to register my webhook, following are the steps that i followed

    1) first i installed ruby with the help rbenv

    2) then i installed twurl using

    gem install twurl
    

    3) now authorize your app by using the following command

    twurl authorize --consumer-key key --consumer-secret secret
    

    4) After running the above command on the command prompt you will get output as

    Go to https://api.twitter.com/oauth/authorize?oauth_consumer_key=xxxxxxxxxxxxx&oauth_nonce=xxxxxxxxxxx&oauth_signature=xxxxxxxxxxxx&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1568986465&oauth_token=xxxxxxx&oauth_version=1.0 and paste in the supplied PIN
    

    5) Just go to the url , twitter page will open with a pin like 9876506 ,just copy the pin and paste in the command line.

    6)Then you will get an output of authorization successful in your cmd.

    7) Now you have authorized your app.

    8) Now before registering the webhook, your call back url that you have given in the details section of your twitter app, should generate a crc token.

    9) If your domain is "www.my_domain.com" create an endpoint as "www.my_domain.com/webhook/twitter" and add the following code to generate crc in your web application.

    def twitterCrcValidation():    
        crc = request.args['crc_token']    
        validation = hmac.new(key=bytes(CONSUMER_SECRET, 'utf-8'),msg=bytes(crc, 'utf-8'),
        digestmod = hashlib.sha256
        )
        digested = base64.b64encode(validation.digest())
        response = {
                   'response_code':200,
                   'response_token': 'sha256=' + format(str(digested)[2:-1])
                    }
        print('responding to CRC call')    
        return json.dumps(response)
    

    10) Now run the following command to register the webhook, in place of put the name of your environment e.g "prod"

    twurl -X POST "/1.1/account_activity/all/<environment>/webhooks.json?url=https://www.my_domain.com/webhooks/twitter"
    

    11) After this you will get a json response as the following

    {"id":"1174326336837472256",
     "url":"https://www.my_domain.com/webhooks/twitter",
     "valid":true,
      "created_timestamp":"2019-09-18 14:16:31 +0000"
     }
    

    12) Finally your url is registered as webhook.

    P.S ----Twitter sends crc token to your endpoint in every 24 hours to check whether the url endpoint is alive or not. So your server containing the url endpoint should be in running condition. I deployed my flask app containing the url endpoint on heroku server. So heruko provides you a free server with a single url unlike ngrok. In ngrok you get a new url whenever you run the server, which is not required for twitter. But heroku server sleeps in every 10 minutes if you do not refresh the website. So twitter might have run the crc check on my site and my site was in sleeping condition due to heroku's policy and my webhook id became invalid.

    You can make your webhook id valid again by running the following command given.

    twurl -X PUT "/1.1/account_activity/all/prod/webhooks/11743xxxxxxxxxx6.json"
    

    then you can get the info about your webhook by running the following command

    twurl -X GET "/1.1/account_activity/all/webhooks.json" --header authorization:bearer token