Working with Twitter's 1.1 API via PHP, OAuth
Twitter has been slowly changing up their API over the last few months. When I first set up a cron to pull my Twitter timeline over a year ago I was able to pull a public RSS feed without too much difficulty. If they were over capacity, either just on the API side or with the entire application, my cron would fail once or twice and catch up during the next run. Things changed this fall. The simple cron started failing days, weeks at a time, and I'd have to do a manual fetch from a local IP address to catch up on the missed data. When I looked into it I realized that the public RSS feed wasn't going to work any more and I'd have to catch up with some of Twitter's recent API 1.1 update, namely OAuth.
Now, I'm not an expert on OAuth or Twitter. I spent a good chunk of time doing research on Twitter's API documentation and a variety of different PHP resources (mostly the cURL section on php.net). All I do with my cron is pull my most recent tweets and update a table on my server, which is later used on my lifestream site. Thanks to Twitter's API changes I'd have to register an application, create an OAuth signature, and construct a cURL request to fetch this data. Below is a rough step-by-step of what I did, which you can follow to fetch your own timeline of tweets using PHP and OAuth.
Step 1: Register an Application
The first part is the easiest. After signing into Twitter you can go to the create an application form and fill out some details of your application. After you fill out the form you'll get four strings of data that are important.
Consumer Key
Consumer Secret
Access Token
Access Token Secret
If you have a hard time finding this fields, just click on the 'OAuth Tool' tab on the application detail page (after you've filled out the form, that is).
Step 2: Create an OAuth Signature
Here is where things get a little tricky. When you make the cURL request to Twitter, you'll be passing an array of fields for authentication. One way they make sure that your request is valid is by checking the signature, which is a hash of those fields and your secret OAuth fields. Below is a rough layout of how this signature is created.
$oauth_hash = '';
$oauth_hash .= 'oauth_consumer_key=YOUR_CONSUMER_KEY&';
$oauth_hash .= 'oauth_nonce=' . time() . '&';
$oauth_hash .= 'oauth_signature_method=HMAC-SHA1&';
$oauth_hash .= 'oauth_timestamp=' . time() . '&';
$oauth_hash .= 'oauth_token=YOUR_ACCESS_TOKEN&';
$oauth_hash .= 'oauth_version=1.0';
$base = '';
$base .= 'GET';
$base .= '&';
$base .= rawurlencode('https://api.twitter.com/1.1/statuses/user_timeline.json');
$base .= '&';
$base .= rawurlencode($oauth_hash);
$key = '';
$key .= rawurlencode('YOUR_CONSUMER_SECRET');
$key .= '&';
$key .= rawurlencode('YOUR_ACCESS_TOKEN_SECRET');
$signature = base64_encode(hash_hmac('sha1', $base, $key, true));
$signature = rawurlencode($signature);
Step 3: Construct the cURL Headers
Now that the signature is created you can go ahead and construct the cURL header. The header is very simple, a two-row array, but one of the rows involves all of the OAuth information. Now that the signature is created we can go ahead and put together the OAuth section and finish up the headers.
$oauth_header = '';
$oauth_header .= 'oauth_consumer_key="YOUR_CONSUMER_KEY", ';
$oauth_header .= 'oauth_nonce="' . time() . '", ';
$oauth_header .= 'oauth_signature="' . $signature . '", ';
$oauth_header .= 'oauth_signature_method="HMAC-SHA1", ';
$oauth_header .= 'oauth_timestamp="' . time() . '", ';
$oauth_header .= 'oauth_token="YOUR_ACCESS_TOKEN", ';
$oauth_header .= 'oauth_version="1.0", ';
$curl_header = array("Authorization: Oauth {$oauth_header}", 'Expect:');
As you might be able to guess already, this would look great broken up in functions and/or class methods. The creation of the OAuth signature and OAuth header is very similar, and there are some chunks of the code that would work great in separate containers. I did write my implementation in a specialized cron class with several abstract helpers, which would be difficult to explain in this short tutorial, but this procedural code also works just fine. If you're interested and capable of object-orientated programming, it is pretty easy to break all this up.
Step 4: Make the cURL Request
Everything is in place to make the actual request! I know that this logic all works fine for fetching a timeline. I'm not sure what, if anything, needs to be modified in the headers if you want to do something more complex like posting an update or manipulating followers.
$curl_request = curl_init();
curl_setopt($curl_request, CURLOPT_HTTPHEADER, $curl_header);
curl_setopt($curl_request, CURLOPT_HEADER, false);
curl_setopt($curl_request, CURLOPT_URL, 'https://api.twitter.com/1.1/statuses/user_timeline.json');
curl_setopt($curl_request, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl_request, CURLOPT_SSL_VERIFYPEER, false);
$json = curl_exec($curl_request);
curl_close($curl_request);
This will pull the mot recent 20 tweets from your timeline! If you have any questions please leave them in a comment below or feel free to reach out to me via twitter (@jpemeric).
-
Jacob Emerick
Nov 5, '12
Hi jiannis, I ran into that very same situation this weekend. You need to add GET params in two different locations: the CURL_OPT_URL field and oauth_array(). For my case I wanted to pass my screen name and a count, so my CURL_OPT_URL was http://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=jpemeric&count=25. The oauth_hash needed two more fields, 'screen_name=jpemeric' '&' 'count=25' (they need to be added in alphabetical order, so count before the oauth keys and screen_name after) and the curl headers needed them as well 'screen_name="jpemeric"' ',' 'count="25"' (again, alphabetical). Notes: you can only pull the most recent 200 that I've been able to see so far, and if you're trying to pull a different screen_name than your own you may need to look into authorization techniques. Let me know if you run into any problems!
Add to this discussion-
Jacob Emerick
Nov 5, '12
Ha, I should work on the formatting of comments a little bit. Sorry about the layout. If I were you the first thing I'd try is doing is passing a second param into json_decode. That will typecase the return into an associative array, which you'll be able to for() loop over easier. Than, for debugging, do a direct var_dump($json_data) to see what's going on. If there is an error you'll be able to see the response here. I can't actually test your code directly now (I don't have access to a dev environment right now) but can try running it later this evening to dig further in if that'll help.
Add to this discussion-
Jacob Emerick
Nov 19, '12
Hey Moeen, were you able to get it working? I noticed that as well... time had to be right on for both the hash and the curl headers. There might be a window of difference between those two and the 'real time', but it ain't much.
-
moeen uddin
Nov 19, '12
Yes, apparently the issue was my time clock setting were set on islamabad. I m in lahore. i corrected that. Helped via twitter forum of course. Thanks. Keep posting. :)
Add to this discussion-
Jacob Emerick
May 10, '13
Hi Jason - yeah, David told me about your app! We've been working on our own little utility but haven't quite wrapped (or tested) it properly for deployment yet. The one you linked up looks interesting, maybe a bit heavy in features. Won't have time until next week to dl and play with, but I'd be curious how he handles the entity replacing. They can be a bit... interesting.
Add to this discussion-
Dave
May 26, '13
Never mind. Just came across your subsequent post, blog.jacobemerick.com/web-development/passing-extra-parameters-to-twitter-via-oauth/, and that did the trick.
-
Jacob Emerick
May 28, '13
Hi Dave - glad you were able to figure this out. I was unplugged over the weekend and didn't see this in time to answer, so sorry about that. If you run into any more issues feel free to let me know and I'll try to get back to you sooner!
Add to this discussion-
Jacob Emerick
Jun 17, '13
Ha, you're welcome! That's exactly why I decided to do this from scratch, all the libraries were crazy big and made a simple thing way too complex. Glad this helped!
Add to this discussion-
Jacob Emerick
Jun 21, '13
Hi Dot,I believe you'd be working with the embedded timelines if you want to use js (client side, etc). [https://dev.twitter.com/docs/embedded-timelines] I don't see anything obvious there if you want a home feed, though there is a couple of different examples and there is something about creating custom widgets as well.If you're looking for the REST method... [statuses/home_timeline] You don't need to have any parameters if you are looking for the authenticated user, so you should be able to use the code from the post and just modify the endpoint.Hope that helps!
Add to this discussion-
Jacob Emerick
Aug 1, '13
Hi Robin - your solution will definitely work as long as Twitter doesn't block it. If the script runs unauthenticated requests too regularly (like, a cron) from a single IP I'd assume that they would block that IP. If you're just looking for a one-time pull, though, I think it'd be fine. Thanks for linking it up here!
-
Robin
Aug 2, '13
I don't think they will, because its the same way as viewing a (public) twitter timeline in your browser... So how will they notice the difference?
-
Jacob Emerick
Aug 5, '13
Hey Robin - the only way they would be able to detect you is by looking at the 'hints'... if you scheduled the script to run at a regular interval from the same IP for too long. I've had the pleasure of working on (and working around) such scripts before in a past life. I'm not sure if Twitter has such tools, and if they did, if they would use them to block a script that only hit every hour or so, but the OAuth method (though complex) does get around it. Otherwise your solution is much easier and would work just fine.
Add to this discussion-
Jacob Emerick
Oct 5, '13
Hey Daniel - glad it worked for you! Do you have your class linked somewhere (like the githubs) to link to or would you rather keep it private?
Add to this discussion-
jyoti
Dec 16, '15
i also have the same issue
Add to this discussion-
Jacob Emerick
Mar 14, '15
Hi Brian - I haven't tried 3-legged yet (only status updates from the parent account), so I'm afraid I won't be much help. Sorry sir!
Add to this discussion-
Jacob Emerick
Aug 11, '16
Hi G! Sorry for the delay, hope this is still useful.Well, I'm not sure how useful :) I didn't see anything wrong with the example in your post... None of the functions look that different or anything. I'd recommend trying a "curl_error()" if you're still using the vanilla curl library to see if your request is being rejected along the way due to a signature being changed.
Add to this discussion