Github notifications are pretty useful in a lot of cases but there are a couple of cases where there are no notifications sent. Examples for this are when someone watches your repository or someone forks it. Yes, these can be seen in your news feed if you watch the repository. But I have a lot of items in the news feed and it is easy to miss things. If I just created a project and want to see how people like it or interested in contributing to it, I would want to know when someone stars it or forks it so I can go check how they use the project or what contributions they want to make.

Initially I had a monitoring script running in one of my servers where it used the Github API to get the watchers and forks count and then sending me an email when someone watches or forks the repository I was interested in. This script was checking the repository status every 5 minutes and seemed unnecessary so I started looking for other options. Github has the option of setting up a webhook and you can specify exactly what events you want to get notified about. That sounded pretty interesting and I was playing with it a little bit. The services like travis-ci and Github pull request builder for Jenkins are using this webhook to get notified about the changes to the repository.

To create a new webhook,

Select particular events

The Github webhook makes a POST to the URL configured in the webhook with the information about what action took place in the repository. The following is the payload sent when someone watches the repository.

Request Headers
1
2
3
4
5
6
7
Request URL: http://192.0.2.10:4567/webhook
Request method: POST
content-type: "application/json"
Expect: ""
User-Agent: "GitHub Hookshot 5e2786c"
X-GitHub-Delivery: "54d2ff00-c501-11e3-9846-ff0dfba08f8b"
X-GitHub-Event: "watch"
JSON Payload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
{
  "action": "started",
  "repository": {
    "id": 8544309,
    "name": "test_rubygem",
    "full_name": "arangamani/test_rubygem",
    "owner": {
      "login": "arangamani",
      "id": 1133812,
      "avatar_url": "https://avatars.githubusercontent.com/u/1133812?",
      "gravatar_id": "6ff021800637b88cbe17d1330cbcc1a5",
      "url": "https://api.github.com/users/arangamani",
      "html_url": "https://github.com/arangamani",
      "followers_url": "https://api.github.com/users/arangamani/followers",
      "following_url": "https://api.github.com/users/arangamani/following{/other_user}",
      "gists_url": "https://api.github.com/users/arangamani/gists{/gist_id}",
      "starred_url": "https://api.github.com/users/arangamani/starred{/owner}{/repo}",
      "subscriptions_url": "https://api.github.com/users/arangamani/subscriptions",
      "organizations_url": "https://api.github.com/users/arangamani/orgs",
      "repos_url": "https://api.github.com/users/arangamani/repos",
      "events_url": "https://api.github.com/users/arangamani/events{/privacy}",
      "received_events_url": "https://api.github.com/users/arangamani/received_events",
      "type": "User",
      "site_admin": false
    },
    "private": false,
    "html_url": "https://github.com/arangamani/test_rubygem",
    "description": "This is a simple test gem used for testing Rubygems API Client. Do NOT use.",
    "fork": false,
    "url": "https://api.github.com/repos/arangamani/test_rubygem",
    "forks_url": "https://api.github.com/repos/arangamani/test_rubygem/forks",
    "keys_url": "https://api.github.com/repos/arangamani/test_rubygem/keys{/key_id}",
    "collaborators_url": "https://api.github.com/repos/arangamani/test_rubygem/collaborators{/collaborator}",
    "teams_url": "https://api.github.com/repos/arangamani/test_rubygem/teams",
    "hooks_url": "https://api.github.com/repos/arangamani/test_rubygem/hooks",
    "issue_events_url": "https://api.github.com/repos/arangamani/test_rubygem/issues/events{/number}",
    "events_url": "https://api.github.com/repos/arangamani/test_rubygem/events",
    "assignees_url": "https://api.github.com/repos/arangamani/test_rubygem/assignees{/user}",
    "branches_url": "https://api.github.com/repos/arangamani/test_rubygem/branches{/branch}",
    "tags_url": "https://api.github.com/repos/arangamani/test_rubygem/tags",
    "blobs_url": "https://api.github.com/repos/arangamani/test_rubygem/git/blobs{/sha}",
    "git_tags_url": "https://api.github.com/repos/arangamani/test_rubygem/git/tags{/sha}",
    "git_refs_url": "https://api.github.com/repos/arangamani/test_rubygem/git/refs{/sha}",
    "trees_url": "https://api.github.com/repos/arangamani/test_rubygem/git/trees{/sha}",
    "statuses_url": "https://api.github.com/repos/arangamani/test_rubygem/statuses/{sha}",
    "languages_url": "https://api.github.com/repos/arangamani/test_rubygem/languages",
    "stargazers_url": "https://api.github.com/repos/arangamani/test_rubygem/stargazers",
    "contributors_url": "https://api.github.com/repos/arangamani/test_rubygem/contributors",
    "subscribers_url": "https://api.github.com/repos/arangamani/test_rubygem/subscribers",
    "subscription_url": "https://api.github.com/repos/arangamani/test_rubygem/subscription",
    "commits_url": "https://api.github.com/repos/arangamani/test_rubygem/commits{/sha}",
    "git_commits_url": "https://api.github.com/repos/arangamani/test_rubygem/git/commits{/sha}",
    "comments_url": "https://api.github.com/repos/arangamani/test_rubygem/comments{/number}",
    "issue_comment_url": "https://api.github.com/repos/arangamani/test_rubygem/issues/comments/{number}",
    "contents_url": "https://api.github.com/repos/arangamani/test_rubygem/contents/{+path}",
    "compare_url": "https://api.github.com/repos/arangamani/test_rubygem/compare/{base}...{head}",
    "merges_url": "https://api.github.com/repos/arangamani/test_rubygem/merges",
    "archive_url": "https://api.github.com/repos/arangamani/test_rubygem/{archive_format}{/ref}",
    "downloads_url": "https://api.github.com/repos/arangamani/test_rubygem/downloads",
    "issues_url": "https://api.github.com/repos/arangamani/test_rubygem/issues{/number}",
    "pulls_url": "https://api.github.com/repos/arangamani/test_rubygem/pulls{/number}",
    "milestones_url": "https://api.github.com/repos/arangamani/test_rubygem/milestones{/number}",
    "notifications_url": "https://api.github.com/repos/arangamani/test_rubygem/notifications{?since,all,participating}",
    "labels_url": "https://api.github.com/repos/arangamani/test_rubygem/labels{/name}",
    "releases_url": "https://api.github.com/repos/arangamani/test_rubygem/releases{/id}",
    "created_at": "2013-03-03T23:17:23Z",
    "updated_at": "2014-04-15T05:04:11Z",
    "pushed_at": "2014-04-15T05:04:11Z",
    "git_url": "git://github.com/arangamani/test_rubygem.git",
    "ssh_url": "git@github.com:arangamani/test_rubygem.git",
    "clone_url": "https://github.com/arangamani/test_rubygem.git",
    "svn_url": "https://github.com/arangamani/test_rubygem",
    "homepage": "https://rubygems.org/gems/test_rubygem",
    "size": 216,
    "stargazers_count": 1,
    "watchers_count": 1,
    "language": "Ruby",
    "has_issues": true,
    "has_downloads": true,
    "has_wiki": true,
    "forks_count": 0,
    "mirror_url": null,
    "open_issues_count": 1,
    "forks": 0,
    "open_issues": 1,
    "watchers": 1,
    "default_branch": "master"
  },
  "sender": {
    "login": "arangamani",
    "id": 1133812,
    "avatar_url": "https://avatars.githubusercontent.com/u/1133812?",
    "gravatar_id": "6ff021800637b88cbe17d1330cbcc1a5",
    "url": "https://api.github.com/users/arangamani",
    "html_url": "https://github.com/arangamani",
    "followers_url": "https://api.github.com/users/arangamani/followers",
    "following_url": "https://api.github.com/users/arangamani/following{/other_user}",
    "gists_url": "https://api.github.com/users/arangamani/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/arangamani/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/arangamani/subscriptions",
    "organizations_url": "https://api.github.com/users/arangamani/orgs",
    "repos_url": "https://api.github.com/users/arangamani/repos",
    "events_url": "https://api.github.com/users/arangamani/events{/privacy}",
    "received_events_url": "https://api.github.com/users/arangamani/received_events",
    "type": "User",
    "site_admin": false
  }
}

Here is the simple ruby code written using Sinatra.

The Sinatra App
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
require 'sinatra'
require 'json'
require 'mail'

configure do
  set :bind, '192.0.2.10'
end

post '/webhook' do
  event = request.env['HTTP_X_GITHUB_EVENT']
  case event
  when 'watch'
    payload = JSON.parse(request.body.read)
    repo = payload['repository']['full_name']
    user = payload['sender']['login']
    user_url = payload['sender']['html_url']
    puts "This is a watch event on '#{repo}' by '#{user}': #{user_url}"
    mail = Mail.new do
      from "noreply@example.com"
      to "me@arangamani.net"
      subject "Repo #{repo} was starred by #{user}"
      body "Reposiroty: #{repo}\n" +
        "User: #{user}\n" +
        "User URL: #{user_url}\n" +
        "Total Stars: #{payload['repository']['stargazers_count']}\n"
    end
    mail.deliver!
  when 'fork'
    payload = JSON.parse(request.body.read)
    repo = payload['repository']['full_name']
    forkee_name = payload['forkee']['owner']['login']
    forkee_type = payload['forkee']['owner']['type']
    forked_repo = payload['forkee']['full_name']
    forkee_url = payload['forkee']['owner']['html_url']
    fork_url = payload['forkee']['html_url']
    puts "This is a fork event on '#{repo}' by '#{forkee_name}' (#{forkee_type})."
    puts "The fork '#{forked_repo}' is avaialble at #{fork_url}"
    mail = Mail.new do
      from "noreply@example.com"
      to "user@example.com"
      subject "Repo #{repo} was forked by #{forkee_name}"
      body "Reposiroty: #{repo}\n" +
        "Forked by: #{forkee_name} (#{forkee_type})\n" +
        "Forked repo #{forked_repo} available at: #{fork_url}\n" +
        "Forkee URL: #{forkee_url}\n" +
        "Total Forks: #{payload['repository']['forks_count']}\n"
    end
    mail.deliver!
  else
    puts "Received '#{event}' type event. Don't know what to do."
  end
  # This block is not required -- just for fun
  content_type 'application/json'
  JSON.pretty_generate(
    {
      message: {
        type: 'appreciation',
        body: 'Hey Github! Thanks for letting me know'
      }
    }
  )
end

Comments