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

jiannis
Hi. Do you know how I could pull more than 20 tweets? Passing the 'count' parameter in a CURLOPT_POSTFIELDS array doesn't work.
Thanks!
Jacob Emerick
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!
jiannis
Hi Jacob,
thanks for the prompt reply. I tried out your solution, but my tweet count is still the same. The error_log says:
PHP Fatal error: Cannot use object of type stdClass as array in /home1/changem1/public_html/twitter.php on line 77
Here's the code, in case you can figure it out.
Thanks!!
<pre>
function buildBaseString($baseURI, $method, $params) {
$r = array();
ksort($params);
foreach($params as $key=>$value){
$r[] = "$key=" . rawurlencode($value);
}
return $method."&" . rawurlencode($baseURI) . '&' . rawurlencode(implode('&', $r));
}
function buildAuthorizationHeader($oauth) {
$r = 'Authorization: OAuth ';
$values = array();
foreach($oauth as $key=>$value)
$values[] = "$key=\\"" . rawurlencode($value) . "\\"";
$r .= implode(', ', $values);
return $r;
}
$url = "https://api.twitter.com/1.1/statuses/home_timeline.json?count=100&screen_name=SCREEN_NAME";
$oauth_access_token = ACCESS_TOKEN;
$oauth_access_token_secret = ACCESS_TOKEN_SECRET;
$consumer_key = CONSUMER_KEY;
$consumer_secret = CONSUMER_SECRET;
$oauth = array( 'count' => '100',
'oauth_consumer_key' => $consumer_key,
'oauth_nonce' => time(),
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_token' => $oauth_access_token,
'oauth_timestamp' => time(),
'oauth_version' => '1.0',
'screen_name' => SCREEN_NAME
);
$base_info = buildBaseString($url, 'GET', $oauth);
$composite_key = rawurlencode($consumer_secret) . '&' . rawurlencode($oauth_access_token_secret);
$oauth_signature = base64_encode(hash_hmac('sha1', $base_info, $composite_key, true));
$oauth['oauth_signature'] = $oauth_signature;
// I TRIED PUSHING THE DATA IN CURLOPT_POSTFIELDS. DOESN'T WORK
//$data = array("count" => '100',
// 'screen_name' => 'honigstudios'
// );
//$params = json_encode($data);
// Make Requests
$header = array(buildAuthorizationHeader($oauth), 'Expect:');
$options = array( CURLOPT_HTTPHEADER => $header,
// CURLOPT_POST => 1,
// CURLOPT_POSTFIELDS => http_build_query($data),
// CURLOPT_VERBOSE => true,
CURLOPT_HEADER => false,
CURLOPT_URL => $url,
//CURLOPT_URL => $url. (strpos($url, '?') === FALSE ? '?' : ''). http_build_query($params),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => false
);
$feed = curl_init();
curl_setopt_array($feed, $options);
$json = curl_exec($feed);
curl_close($feed);
$json_data = json_decode($json);
// THIS SHOULD BE 100. IT'S INSTEAD 1
print_r (count ($json_data) );
for($i=0; $i<=$limit; $i++)
{
// HERE COMES THE ERROR AT THE ERROR_LOG
$text = $json_data[$i]->text;
echo $i . " " . $text . "<br/>";
}
</pre>
Jacob Emerick
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.
jiannis
oh - ouch! I just saw the formatting; sorry about that!
I tried out your suggestion. I get an error code 32 from twitter "could not authenticate you".
I'm off to google to see if it can help...
moeen uddin
I was getting 401. Seems like my system time setting were not acurate. I changed time setting to my best representing time settings.
Jacob Emerick
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
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. :)
Independent Software Developer
Thanks a lot for this, I'm new to oAuth and this is the first tutorial that makes sense as to how to get up and running.
Jason Gegere
I had a service online similar to what you are doing. I created a application called "huffer" as a twitter app years ago. During a recent PHP meetup, a conversation about twitter oAuth came up and a few names where shared. As I was researching some things to tweak my app I came across your post.
Have you looked into this? willianveiga/twitteroauth
Jacob Emerick
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.
Dave
Jacob, would appreciate your guidance with the same issue jiannis faced above.
I applied your script and it's working beautifully with default settings. However, I'm getting "message: Could not authenticate you / code: 32" when I add a count.
Here are the four things I'm doing to change the count from 20 to 200:
<pre>
1) Adding >>$oauth_hash .= 'count=200&';<< before the oauth_consumer_key item and adding >>$oauth_hash .= 'screen_name=USERNAME';<< after the oauth_version item (and adding the needed ampersand to the latter)
2) Changing the rawurlencode URL to >>https://api.twitter.com/1.1/statuses/home_timeline.json?count=200<<
3) Adding >>$oauth_header .= 'count="200", ';<< before the oauth_consumer_key item and adding >>$oauth_header .= 'screen_name="USERNAME", ';<< after the oauth_version item
4) Changing CURLOPT_URL to >>https://api.twitter.com/1.1/statuses/home_timeline.json?count=200<<
</pre>
I'd really appreciate your guidance. Thank you for this post!
Dave
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
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!
Peter Virtanen
THANK YOU! Two days no success and when i copy pasted your truly simple code I got my twitter api working again. I can't thank you enough. (two days having "Could not authenticate" with other solutions)
It's nice and simple code without any mumbel jumble like all other libraries had within.
-Peter
Jacob Emerick
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!
Dot
Hi, how would you retrieve a user's home feed , instead of their timeline. Do you also know how to do this in javascript ?
Jacob Emerick
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!
Babar
Thanks for such a nice post. It really helped me. Thanks again.
Hammy
Amazing - with a few tweeks to this one I pulled a Magento module together.
Thanks so much.
Robin
Due to deprecating Twitter API v1.0, developers need to create oAuth authentication to retrieve tweets. This script does exactly the same, without the OAuth hazzle, so its much easier to use. Its only less advanced as Twitter API v1.1. It will retrieve tweets (and retweets) with their avatar, username and post date in JSON format: github.com/robinbonnes/Custom-Twitter-PHP-API
Jacob Emerick
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
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
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.
Daniel Carvalho
Thank you very much for this really great working example.
This provided a great "ground truth", which made formulating a class fairly straightforward.
Jacob Emerick
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?
Rob
Hey Jacob,
Great post. You really made something that I thought would be annoying into something quite simple. I pasted this code into my own solution and it worked.
A question though: If I wanted to change the URL from returning my own timeline to something like the results of a search (say a URL like http://api.twitter.com/1.1/search/tweets.json?q=ABC&result_type=recent&count=10 where ABC is my search query), how would I have to modify your code for this? Simply changing the two URLs in your code above gives me a "Could not authenticate you" error.
jyoti
i also have the same issue
Abhi
Awesome! It works perfectly. Thanks a lot, I was struglling to get this done. Thanks indeed.
Brian
Hello, great article. Do you have an example where you are updating the status? I'm having trouble wrapping my head around authenticating a user and then posting to their timeline from our app. The I do the redirect to authorize app and then return to our app. At this point I have tried to use the app's access token and key and I've tried using the user's access token and key return from the authentication process. Neither seems to be working. If you have any examples on this, it would be great. Thank you much.
Jacob Emerick
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!
G
Hi Jacob,
My variant of this (sgowtham.com/journal/twitter-status-update-with-api-1-1-using-php/) stopped working after upgrading my workstation to RHEL 7.2 (runs PHP 5.5 by default; was previously running RHEL 6.x and PHP 5.3). Have you experienced anything like it on your end?
I have verified that PHP 5.5 itself works fine. I have added checks for the existence of every function (e.g. function_exits('function_name')), and it checks out fine with no error message. But the Twitter timeline doesn't get updated with the new status.
For the time being, I am using Abraham William's TwitterOAuth. It works really well but I'd feel a little better knowing/understanding the code rather than using a third-party one.
Thank you for your time and help. Hope all is well :)
Jacob Emerick
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.