Creating an Instagram Subscription API with Azure Mobile Services

Windows Azure Mobile Services has been an exciting tool I’ve been using to develop my mobile applications.  One of the key things when building several kinds of mobile applications is integration with several of the big social networks.  Instagram becoming the way to share photos was one of those integration points I needed in my application.  Instagram provides  a “real-time” API service which uses the PubSubHub model,  details about the real-time API can be found on Instagram’s developer site. 

My application required the need to have images from Instagram show up in a user’s feed as it was available.  At first I thought about just polling the Instagram feed’s using a Scheduled job (which is a part of the mobile service) however this solution did not scale well due to the limited number of SQL Azure connections in the service.  This lead me to the real-time API to have Instagram tell me when to update my application.  As a part of this I also wanted the user’s to have the ability to specify the tag for which they would like to follow, as well as the username (in many cases their own) they would like to follow as well.  Given these requirements I needed a generic enough API which would allow my application to subscribe to these objects in Instagram and then have them populate in my application by storing in a table. 

So enough background let’s get to the code. 

Step 1. Create an InstagramSubscriptions table.

There are a few tables and services I needed in my solution.  We want to create these here first, this way when we get to our API Controllers we are ready to go.

The first Item is a table to keep track of the subscriptions.  This table is needed as when a new item is added to Instagram the Instagram publishing service will call your API method with the subscription payload.  The payload does not contain any media information, a second call from your API will need to be made to actually get the items.  Secondly, I needed a way to tie this subscription back to a user. This table also provides a quick way for me to delete a subscription from the Instagram service without having to first call Instagram’s subscription API. 

  1. Create a Table named “InstagramSubscriptions”
  2. Add the following Columns all of type String:
    1. SubscriptionId
    2. UserId
    3. ObjectType
    4. ObjectId

 

Step 3.  Create the Item Push API

The Push API as I call it will be the callback method for which Instagram will use when there is a new item for the subscriptions in your application.  This callback will receive a payload with the subscription details for which new items have been added.  Once this payload is received you will have the ability to call the other Instagram API methods to get the actual data (images/video) you can then pull into your app.  In the Mobile App Service create a new API, I called mine “Instagram_push”.  You will need a GET method and a POST method which is accessible to “Everyone” as Instagram will not be able to authenticate and/or use your Application key.  To keep the process secure Instagram sends a Verify_Token, which is setup when you create your subscription (we will see this in the next step).  You should always validate the Verify_Token when new subscription requests are sent as this is the only way to ensure this is a valid call from Instagram.

Here are the two methods I created to be used as my callback method when Instagram needs to send me an update for new items, I’ve left the spot when you implement your logic blank in this sample, but in my case I update some tables and perform other logic which retrieves the items in this spot when Instagram sends me an update:

exports.post = function(request, response) {

    var bodyObject = JSON.Parse(request.body); // Parse the response into a JSON object for use in my Script
    console.log("Instagram Push: " + new Date());
    console.log(request.body);

// ADD YOUR LOGIC HERE!

//TYPICALLY CALL INSTAGRAM API TO GET THE NEW ITEMs

};

exports.get = function(request, response) {
    var hubchallenge = request.query["hub.challenge"];
    //console.log ("Hub Challenge: "  + hubchallenge)
    response.send(statusCodes.OK, hubchallenge);
};

 

Step 2.  Create the Subscription Service API

The second half of what I needed was to have a method for my application to automate the creation of subscriptions with Instagram.  I also wanted to create a way to remove these subscriptions if the user choses to do so.  To accomplish this I create a second API interface which handles the subscription service.  This service will take in some parameters in the body:

  • igObjectType – This is the type of subscription.  should be “tag”, “user”, “geo”
  • igName – The string for the tag or the username for which we are creating this subscription.
  • userId – the user Id I use to tie this subscription in my application.

I also set some environment variables such as the Push API Url which will be used as our call back.  You should replace this URL with your Mobile Service URL.

 

SECURITY HINT: unlike the Item Push API above this API should be secured using the Mobile service methods.  I chose to lock this service down by only allowing authenticated users.  You should use what is best for your application.

exports.post = function(request, response) {
    // Use "request.service" to access features of your mobile service, e.g.:
    //   var tables = request.service.tables;
    //   var push = request.service.push;
    var sql = request.service.mssql;
    var instagramPushUrl = "https:// [YOUR MOBILE SERVICE URL] /api/instagrampush/";
    var igName = request.body.igName;
    var igObjectType = request.body.type;
    var streamId = request.body.streamId;
    var createSubscriptionQuery = "INSERT INTO InstagramSubscriptions (userId, subscriptionId, objectType, objectId) VALUES (?, ?, ?, ?)";

//Ensure a valid user Id for my applications.     

if (!userId) {
        request.respond(403, { message: 'Bad request! No User Id for this subscription' });
    }

    if (!igObjectType) {
        igObjectType = "tag"; //Set Default if not passed
    }

    var httpRequest = require('request');
    if (igName) {
        var url = 'https://api.instagram.com/v1/subscriptions?client_id=[YOUR CLIENT ID]&client_secret=[YOUR CLIENT SECRET]&verify_token=[YOUR VERIFY TOKEN]';
        var msgBody = {
            aspect: "media",
            callback_url: instagramPushUrl,
            object: igObjectType,
            object_id: igName
        };

        httpRequest({
            uri: url,
            method: "POST",
            form: msgBody,

        }, function(err, response, body) {
                //console.log("IG Response: " + body);
                if (err) {
                    console.error("IG Subscribe Error: " + err);
                    request.respond(500, { message: 'IG NOT Subscribed for 1 ' + igObjectType + ' with name ' + igName + '!' });
                }
                else if (response.statusCode !== 200) {
                    console.error("IG Subscribe Error: " + err);
                    request.respond(500, { message: 'IG NOT Subscribed for 2 ' + igObjectType + ' with name ' + igName + '!', err: body, url: url + msgBody });
                }
                else {
                    var bodyObject = JSON.parse(body);                   
                    //console.log ("Result from Subscription: " + bodyObject);                   
                    //console.log("Subscription Id: " + subscription.id);
                   
                    var subscription = bodyObject.data; 
                   
//Update my DB

sql.query(createSubscriptionQuery, [userId, subscription.id, igObjectType, igName], {
                        success: function(result) {
                            request.respond(statusCodes.OK, { message: 'IG Subscribed for user ' + igObjectType + ' ' + igName + '!', result: body });
                        },
                        error: function(error) {
                            console.error(error);
                            console.log('IG Subscription Created but not logged for ' + igObjectType + ' with name ' + igName + '!' + ' and SubscriptionId ' + subscription.id);
                            request.respond(statusCodes.OK, { message: 'IG Subscription Created but not logged for ' + igObjectType + ' with name ' + igName + '!', result: body });
                        }
                    });
                }
            });
    }
    else {
        request.respond(404, { message: 'User ' + igName + ' not found!' });
    }
};

//DELETE METHOD

exports.delete = function(request, response) {
    var sql = request.service.mssql;
    var subscriptionId = request.query.subscriptionId;
    var deleteQuery = "DELETE FROM InstagramSubscriptions WHERE subscriptionId = ?";

    if (!subscriptionId) {
        request.respond(403, { message: 'Bad request! SubscriptionId is required' });
    }

    var httpRequest = require('request');

    var url = 'https://api.instagram.com/v1/subscriptions?client_id=[YOUR CLIENT ID]&client_secret=[YOUR CLIENT SECRET]&id=' + subscriptionId;

    httpRequest({
        uri: url,
        method: "DELETE",
    }, function(err, response, body) {
            //console.log("IG Response: " + body);
            if (err) {
                console.error("IG Subscribe Error: " + err);
                request.respond(500, { message: 'IG NOT delete Subscribed for subscription with id ' + subscriptionId });
            }
            else if (response.statusCode !== 200) {
                console.error("IG Subscribe Error: " + err);
                request.respond(500, { message: 'IG NOT delete Subscribed for subscription with id ' + subscriptionId });
            }
            else {
                sql.query(deleteQuery, [subscriptionId], {
                    success: function(result) {
                        request.respond(200, { meta: { code: 200 }, data: [] });
                    },
                    error: function(error) {
                        console.error(error);                       
                        console.log("Failed to update database after deleting subscription with id: " + subscriptionId);
                        request.respond(200, { meta: { code: 400 }, data: { subscriptionId: subscriptionId } });
                    }
                });
            }
        });
};