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.
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