Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / DevOps

Push Notification (PubSubHubBub) from Youtube to C# Web Service

4.45/5 (5 votes)
13 Feb 2018CPOL2 min read 14.3K  
Youtube push notification

Introduction

Google came up with a notification service which is known as PubSubHubBub. This system is used for giving prompt notification on any updates on a website. This article is about getting notification from youtube when a youtuber uploads video.

Background

Getting notification requires 3 major steps to be followed:

  1. Subscribing to Channel
  2. Receiving Subscription Confirmation
  3. Receiving Upload Notification

You can find the information at Youtube Data API Documentation.

Information is given in a straight forward manner. But in reality, setting up a web service is a bit tricky.

Test and find channel subscription details here.

Using the Code

Following are the steps for a complete Notification Service.

1. Channel Subscription

For subscribing to a channel, a web request must be created to "subscribeUrl".

The most important parameters to be sent are:

  • hub.mode = subscribe/unsubscribe
  • hub.verify_token = Unique Identifier, which can be used as a verification when youtube sends notification
  • hub.verify = sync/async (Recommended async, as sync seems bit problematic)
  • hub.callback = URL where youtube will send response to, P.S. localhost is not supported, needs to be deployed to a server
  • hub.topic = Youtube channel notification URL with channelId in the query string
C#
[HttpGet]
public void Subscribe (string channelId) {
   try {
       var callBackUrl = "YOUR CALL BACK URL HERE";
       var topicUrl = $"https://www.youtube.com/xml/feeds/videos.xml?channel_id={channelId}";
       var subscribeUrl = "https://pubsubhubbub.appspot.com/subscribe";
       string postDataStr = $"hub.mode=subscribe&hub.verify_token={Guid.NewGuid().ToString()}&
       hub.verify=async&hub.callback={HttpUtility.UrlEncode(callBackUrl)}&
       hub.topic={HttpUtility.UrlEncode(topicUrl)}";
       byte[] postData = Encoding.UTF8.GetBytes(postDataStr);

       HttpWebRequest request = (HttpWebRequest)WebRequest.Create(subscribeUrl);
       request.Method = "POST";
       request.ContentType = "application/x-www-form-urlencoded";
       request.ContentLength = postData.Length;

       Stream requestStream = request.GetRequestStream();
       requestStream.Write(postData, 0, postData.Length);
       requestStream.Flush();
       requestStream.Close();

       HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse();
       if (request.HaveResponse)
       {
            Stream responseStream = webResponse.GetResponseStream();
            StreamReader responseReader = new StreamReader(responseStream, Encoding.UTF8);
            responseReader.ReadToEnd();
       }
       else
       {
           throw new Exception("Didn't receive any response from the hub");
       }

   }
   catch (Exception ex){
       throw new Exception($"Error publishing Channel ID : {channelId}", ex);
   }
}

2. Receive Subscription Confirmation & Youtube Notification

For receiving confirmation and notification, same API callback URL is used. The tricky part is, when receiving subscription confirmation, the call method used is "GET" and for receiving Youtube notification it is "POST".
Due to which the method should have both GET and POST verbs. Removing one would result in a 409 error.

For notification, an Atom Feed is used for PUSH notification.

Create a YoutubePush Class to set Atom Feed:

C#
public class YoutubeNotification
{
   public string Id { get; set; }
   public string VideoId { get; set; }
   public string ChannelId { get; set; }
   public string Title { get; set; }
   public string Link { get; set; }
   public Author Author { get; set; }
   public string Published { get; set; }
   public string Updated { get; set; }
   public bool IsNewVideo
   {
       get
       {
            return Published == Updated && !string.IsNullOrEmpty(Published);
       }
   }
}

 public class Author
 {
    public string Name { get; set; }
    public string Uri { get; set; }
 }

When receiving subscription confirmation, it is necessary to return the hub.challange information as Response body. The parameter is sent in the query string only when subscribing, this can be used to distinguish between subscription and notification.

Atom feed needs to be converted into SyndicationFeed, this requires a reference to System.ServiceModel.Syndication. To get the reference, just add System.ServiceModel as service reference.

C#
[HttpGet]
[HttpPost]
 public async Task<HttpResponseMessage> NotificationCallback()
 {
    try
    {
       var re = HttpContext.Current.Request;
       string challenge = re.QueryString["hub.challenge"];

       //Incoming Notification from Youtube
       if (string.IsNullOrEmpty(challenge))
       {
          var stream = re.InputStream;
          var data = ConvertAtomToSyndication(stream);
          challenge = "OK";

          // Add your logic
        }
        else
        {
           //Add your subscription logic
        }

        return new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent(challenge),
            ReasonPhrase = challenge,
            StatusCode = HttpStatusCode.OK
        }
    }
    catch (Exception ex)
    {
       //Handle Exception Accordingly.
    }
 }

 private YoutubeNotification ConvertAtomToSyndication(Stream stream)
 {
     using(var xmlReader = XmlReader.Create(stream))
     {
         SyndicationFeed feed = SyndicationFeed.Load(xmlReader);
         var item = feed.Items.FirstOrDefault();
         return new YoutubeNotification()
         {
             ChannelId = GetElementExtensionValueByOuterName(f, "channelId"),
             VideoId = GetElementExtensionValueByOuterName(f, "videoId"),
             Title = f.Title.Text,
             Published = f.PublishDate.ToString("dd/MM/yyyy"),
             Updated = f.LastUpdatedTime.ToString("dd/MM/yyyy")
         };
     }
 }

 private string GetElementExtensionValueByOuterName(SyndicationItem item, string outerName)
 {
    if (item.ElementExtensions.All(x => x.OuterName != outerName)) return null;
    return item.ElementExtensions.Single(x => x.OuterName == outerName).GetObject<XElement>().Value;
 }

Points of Interest

I hope this helps you to understand the working of PubSubBubHub (Push Notification).

History

  • First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)