Using Facebook’s Realtime Updates and Subscription API

Facebook’s real-time updates allow us to declare our interest in certain information about our users and have Facebook notify us when it changes instead of us continually querying the API. Although Facebook recommends we retain a minimum amount of user data, it’s prudent to maintain a balance. Indeed, it makes more sense to store certain information, such as a user’s name and her list of friends, than to continually query for it. Subscribing to information updates helps ensure that our data always stays current.

There are of course other uses for real-time updates – and indeed we’ll take a look at one in this article: an example application which emails people when others “unfriend” them. (Actually that’s not strictly true – it tells you when someone is no longer your “friend” regardless of who did the unfriending.)

Getting Started

If you’ve ever built a Facebook application, registering the app will be second nature for you. Go to https://developers.facebook.com, click Apps, then click Create New App, and follow the instructions. Save the application ID and secret because you’ll need that later to establish the connection to Facebook.

Also, the application will need to be publicly accessible for Facebook to “ping” it. You might want to consider using a service such as AppFog, Heroku, or PagodaBox, or simply host it yourself on an accessible server.

Once you have registered with Facebook and know where you will host the app, a good point to start is to either download a skeleton project for your favorite framework (this article uses this Slim-based skeleton by Timothy Boronczyk) or you can download the article’s example code from GitHub.

First, we need to make a few additions to skeleton’s composer.json file. We’ll install three additional libraries: the Facebook SDK to access their API, PHPMailer to easily send emails, and a port of the popular JavaScript library Underscore.

{
    "require": {
        ...
        "facebook/php-sdk": "dev-master",
        "phpmailer/phpmailer": "dev-master",
        "underscore/underscore.php": "dev-master"
    }
}

Next, create a configuration file by copying config/config.php.example to config/config.php, set your specific database credentials, and make the following additions:

'facebook.app_id'       => 'FACEBOOK-APP-ID',
'facebook.app_secret'   => 'FACEBOOK-APP-SECRET',
'facebook.verify_token' => 'FACEBOOK-VERIFY-TOKEN',
    
'smtp.host'             => 'SMTP-HOST',
'smtp.port'             => SMTP-PORT,
'smtp.encryption'       => 'tls', // or 'ssl'
'smtp.username'         => 'SMTP-USERNAME',
'smtp.password'         => 'SMTP-PASSWORD',
'smtp.from_address'     => 'no-reply@example.com',
'smtp.from_name'        => 'SitePoint Facebook Real-Time Tutorial',

Obviously you’ll need to provide your own values as you go along.

Now add the following to include/services.php, which handles the dependency injections, so that our application has easy access to the Facebook SDK and PHPMailer libraries:

$c['facebook'] = function($c) {
    $config = $c['config'];
    return new Facebook(array(
        'appId' =>  $config['facebook.app_id'], 
        'secret' => $config['facebook.app_secret'],
        'cookie' => true,
    ));
};

$c['phpmailer'] = function($c) {
    $config = $c['config'];

    $mail = new PHPMailer();

    $mail->IsSMTP();
    $mail->Host = $config['smtp.host'];
    $mail->Port = $config['smtp.port'];
    $mail->SMTPAuth = true;
    $mail->Username = $config['smtp.username'];
    $mail->Password = $config['smtp.password'];              
    $mail->SMTPSecure = $config['smtp.encryption'];

    $mail->From = $config['smtp.from_address'];
    $mail->FromName = $config['smtp.from_name'];

    $mail->WordWrap = 100;
    $mail->IsHTML(true);

    return $mail;
};

Finally, we need to set up our database schema. (I’m going to use MySQL, although NotORM – the ORM bundled with the skeleton application – works with any database that supports PDO.) There’s only one table:

CREATE  TABLE users (
    id INTEGER NOT NULL AUTO_INCREMENT,
    fb_id VARCHAR(64),
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) NOT NULL,
    friends TEXT,
    fb_access_token VARCHAR(255),
    fb_access_token_expires INTEGER,

    PRIMARY KEY (id) ,
    UNIQUE INDEX fb_id_UNIQUE (fb_id ASC)
);

Authorizing the Application

In order to act on the behalf of our users, we need to allow them to authorize our application. We’ll keep things simple and just create a page with a Facebook sign-in button. Clicking the button will redirect the user to Facebook where she will be asked to authorize the application before redirecting to a confirmation callback.

In the callback, we need to do a few things:

  • Exchange the code for an access token.
  • Exchange the access token for a long-term access token.
  • Get the user’s information.
  • Retrieve a list of the user’s friends.
  • Create a database record for the user, storing the long-term access token and the list of her friends.

Facebook has done away with offline access, which we used to be able to get a user access token that lasted forever. Instead, we now request a long-term token. When I tried this, the token indicated it was valid for just under two months and the user will need to re-authenticate when (or just before) it expires. Reauthorization is beyond the scope off this article, but be aware that our application can’t operate forever without the user re-authorizing it.

First, add the route for the front page:

$app->get('/', function () use ($app, $c) {
    $url = $c['facebook']->getLoginUrl(array(
        'scope' => 'email',
        'redirect_uri' => $app->request()->getUrl() . '/confirm',
    ));

    $app->view()->setData(array(
        'login_url' => $url,
    ));

    $app->render('index.html');
});

All this route does is get the login URL – specifying that we need the user’s email address as an additional permission – and set the confirmation callback. The important stuff happens in the callback.

Next, add the route for the confirmation callback:

$app->get('/confirm', function () use ($app, $c) {
    $config = $c['config'];
    $facebook = $c['facebook'];

    // exchange the code for an access token
    $url = sprintf(
        'https://graph.facebook.com/oauth/access_token?client_id=%s&redirect_uri=%s&client_secret=%s&code=%s',
        $config['facebook.app_id'],
        urlencode($app->request()->getUrl() . '/confirm'),
        $config['facebook.app_secret'],
        $app->request()->get('code')
    );
    $response = file_get_contents($url);

    $params = null;
    parse_str($response, $params);
    $token = $params['access_token'];

    // exchange the access token for a long-term token
    $url = sprintf(
        'https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id=%s&client_secret=%s&fb_exchange_token=%s',
        $config['facebook.app_id'],
        $config['facebook.app_secret'],
        $token
    );
    $response = file_get_contents($url);

    $params = null;
    parse_str($response, $params);
    $token = $params['access_token'];
    $tokenExpires = $params['expires'];

    // get the user's information
    $facebook->setAccessToken($token);
    $fb_user = $facebook->api('/me');
    $friends = $facebook->api('/me/friends');

    // create the database entry
    $c['db']->users->insert(array(
        'fb_id'   => $fb_user['id'],
        'name'    => $fb_user['name'],
        'email'   => $fb_user['email'],
        'friends' => serialize($friends['data']),
        'fb_access_token' => $token,
        'fb_access_token_expires' => $tokenExpires
    ));
});

There are a couple of limitations to this simple example:

  1. It assumes that the user agrees to grant our application access to her email address.
  2. It assumes that the user hasn’t previously authorized the application.

Feel free to extend the example by handling denials and build in some sort of mechanism to ask users to re-authorize when the token expires.

Setting up Subscriptions

Facebook’s Subscriptions API provides a REST interface to the application’s subscriptions. A subscription is simply a case of telling Facebook what real-time updates we want to receive. We specify the object – in our case, users – and the fields – friends – we’re interested in.

Note that we subscribe to types of objects, not specific object themselves; a subscription for users means we’ll get update for anyone who’s authorised the application, not just a specific user. Also, real-time updates are limited to certain types of objects and a subset of their fields. Refer to the Realtime Updates documentation for more information.

There are two ways to set up subscriptions: through the developer portal on Facebook, and using the API. In either case, we need to have set a hub challenge callback first because Facebook pings this as part of the validation process.

Whenever we add or modify a subscription, Facebook verifies the subscription by making a GET request to a callback with the following parameters:

  • hub.mode – the string “subscribe”
  • hub.challenge – a random string
  • hub.verify_token – the verify_token value we gave when we created the subscription

Our callback simply checks that hub.mode contains the string “subscribe”, that hub.verify_token matches the one we specified, and all being well simply outputs the value of hub.challenge.

$app->get('/subscriptions', function () use ($app, $c) {
    $req = $app->request();
    $verify = $c['config']['facebook.verify_token'];
    if ($req->get('hub_mode') == 'subscribe' && $req->get('hub_verify_token') == $verify) {
        echo $req->get('hub_challenge');
    }
});

To set up a subscription through the developer portal, go to your application and click “Realtime updates” under “Settings” on the left-hand side. Where it says “Please select an Object you would like to subscribe to”, select “user” and click Confirm. You’ll now see the following form:

realtime-updates-01

Enter “friends” as the object fields, “<YOUR-URL>/subscriptions” for the callback endpoint, and enter a random string for the verify token (also record this random string in your configuration file).

Alternatively, to set up a subscription using the Subscriptions API, make a POST request to https://graph.facebook.com/<APP_ID>/subscriptions. For example:

$facebook->api(
    $config['facebook']['app_id'] . '/subscriptions',
    'POST',
    array(
        'object'       => 'user',
        'fields'       => 'friends',
        'callback_url' => 'http://www.example.com/subscriptions',
        'verify_token' => $config['facebook']['verify_token']
    )
);

Processing Real-Time Updates

The subscription updates themselves are POSTed to our application in JSON format. To process them, we need to set up a callback that verifies the request’s authenticity and then updates our local data.

The code coming up in the next sections all resides in the POST callback:

$app->post('/subscriptions', function () use ($app, $c) {

Verifying the POST Request

It’s strongly advised to verify that the POST request actually came from Facebook. Luckily, this is relatively straightforward. When making the request, Facebook will generate a signature based on the body of the request and our application secret and include it in the X-HUB-SIGNATURE header. We simply check that the header is set, generate the signature ourself, and compare the two.

$req = $app->request();
$headers = $req->headers();

$signature = $headers['X_HUB_SIGNATURE'];
$body = $req->getBody();

$expected = 'sha1=' . hash_hmac('sha1', $body, $facebook->getApiSecret());

if ($signature != $signature) {
	exit();
}

The signature is generated by taking the request body, adding “sha1=” to it the start of it, and then hashing it with SHA1 using the application secret. Once we get this far, we know that the request came from Facebook, so we can proceed onto processing it.

Processing the Update

The update tells us what has changed, but not what it changed to. It’s the developer’s responsibility to query the API to find out the new value. Here’s an example of a notification that tells us that the user identified by 123456789 has friend changes:

{
  "object": "user",
  "entry": [
    {
      "uid": "123456789",
      "id": "123456789",
      "time": 1374846331,
      "changed_fields": [
        "friends"
      ]
    }
  ]
}

It’s possible that we’ll receive a single update at a time or a bunch of updates together – but always for the same object type. That is, we wouldn’t get user updates and page updates in the same request. As such, we need to check the object type first and then iterate through the actual objects to see what fields have changed.

$updates = json_decode($body, true);
if ($updates['object'] == 'user') { 
    foreach ($updates['entry'] as $entry) {
        $uid = $entry['uid'];
        foreach ($entry['changed_fields'] as $field) {
            if ($field == 'friends') {
                ...

We’re only interested in the friends field, but of course you can subscribe to other fields (in which case a switch statement might be better).

For each user object, we can retrieve our saved information from the database using the Facebook UID we just grabbed from the request.

$user = $db->users('fb_id = ?', $uid)->fetch();
if ($user) {
    $data = unserialize($user['friends']);

The unserialized friends data looks something like this:

array(123) {
  [0]=>
  array(2) {
    ["name"]=>
    string(11) "Mick Jagger"
    ["id"]=>
    string(7) "123456789"
  }
  [1]=>
  array(2) {
    ["name"]=>
    string(10) "Keith Richards"
    ["id"]=>
    string(8) "987654321"
  }
...

All we want is an array of the ID’s, so here’s where Underscore comes in, specifically the pluck() function:

$friendIDs = __::pluck($data, 'id');

We are using the Facebook API on behalf of the user so we need set the access token to what we have saved previously in the database. Then we can grab an up-to-date list of friends and run pluck() again it so we have an array to compare with. We simply determine the IDs that are missing from the up-to-date list and then use the API to fetch the corresponding names.

$facebook->setAccessToken($user['fb_access_token']);
$response = $facebook->api('/me/friends');
$friendsData = $response['data'];

$newFriendIDs = __::pluck($friendsData, 'id');
$removedIDs = array_diff($friendIDs, $newFriendIDs);

if (count($removedIDs)) {
    $html = '<p>The following people have un-friended you:</p>';
    $html .= '<ul>';
    foreach ($removedIDs as $id) {
        $friend = $facebook->api($id);
        $html .= '<ul>' . $friend['name'] . '</li>';
    }
    $html .= '</ul>';

    $mail = $c['phpmailer'];
    $mail->AddAddress($user['email'], $user['name']);
    $mail->Subject = 'Someone has un-friended you on Facebook!';
    $mail->Body = $html;
    $mail->Send();
}

There’s one last thing and we’re done; we need to save the latest friends list to the database so that we can use it in future comparisons.

$user->update(array(
    'friends' => serialize($friendsData),
));

Summary

We looked at Facebook’s Realtime Updates and Subscriptions API in this article and saw how incredibly useful they are for keeping locally-stored data up to date by letting us fetch updated data when there’s a change. We demonstrated how they can be used by building an example application that monitors a user’s friends list and lets her know when a change has occurred. Be sure to let me know in the comments if you have other ideas for how they can be useful!

Image via Fotolia

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • ali

    wow this is real help full .. but can u also tech how we can work on FB pages .. to post images

  • Brenton Alker

    Your signature validation has a bit of a bug. It will never fail.
    I think
    `if ($signature != $signature) {`
    should have been
    `if ($signature != $expected) {`

  • Ruben D. Gutiérrez S.

    awesome, i´ll test this turorial to learn this

  • manglesh

    really this so helpful .
    Thanx for sharing .

  • http://www.mindgram.com Jim DeLong

    I’m trying to do this on my godaddy account. Is it possible in a shared environment?

  • Diego ZoracKy

    Very good article, Lukas. Do you know if the subscription at “page/feed” or “user/feed” will announce when any post receives a new “like” or a new “comment” ?