diff --git a/CHANGELOG b/CHANGELOG
index f2ac3b979a2b960407df0c12ded9c073d765819d..0c401a99d48e5745cfd2bbbc8e559576f9e0b020 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -31,6 +31,9 @@ v 8.0.0 (unreleased)
   - Don't notify users without access to the project when they are (accidentally) mentioned in a note.
   - Retrieving oauth token with LDAP credentials
   - Load Application settings from running database unless env var USE_DB=false
+  - Added Drone CI integration (Kirill Zaitsev)
+  - Refactored service API and added automatically service docs generator (Kirill Zaitsev) 
+  - Added web_url key project hook_attrs (Kirill Zaitsev)
 
 v 7.14.1
   - Improve abuse reports management from admin area
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index b0cf5866d41033b9367d10675cc9b91f83eaa350..3a22ed832ac4fb19bdc5b0913b95f87c3eaf6944 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -2,7 +2,7 @@ class Projects::ServicesController < Projects::ApplicationController
   ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_version, :subdomain,
                     :room, :recipients, :project_url, :webhook,
                     :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
-                    :build_key, :server, :teamcity_url, :build_type,
+                    :build_key, :server, :teamcity_url, :drone_url, :build_type,
                     :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
                     :colorize_messages, :channels,
                     :push_events, :issues_events, :merge_requests_events, :tag_push_events,
diff --git a/app/models/project.rb b/app/models/project.rb
index 69f9af91c51d0b1c1fd4dea5dff7465e5f57a16c..8e33a4b2f0f9781d93d46681977599a598fc0eab 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -73,6 +73,7 @@ class Project < ActiveRecord::Base
   has_many :services
   has_one :gitlab_ci_service, dependent: :destroy
   has_one :campfire_service, dependent: :destroy
+  has_one :drone_ci_service, dependent: :destroy
   has_one :emails_on_push_service, dependent: :destroy
   has_one :irker_service, dependent: :destroy
   has_one :pivotaltracker_service, dependent: :destroy
@@ -613,6 +614,7 @@ class Project < ActiveRecord::Base
       name: name,
       ssh_url: ssh_url_to_repo,
       http_url: http_url_to_repo,
+      web_url: web_url,
       namespace: namespace.name,
       visibility_level: visibility_level
     }
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index 803402c83ee3b817aded25b23670295fac267ceb..88186113c68289655947a812b346e8353ed8868b 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -25,12 +25,24 @@ class CiService < Service
   def category
     :ci
   end
-
+  
+  def valid_token?(token)
+    self.respond_to?(:token) && self.token.present? && self.token == token
+  end
+  
   def supported_events
     %w(push)
   end
 
-  # Return complete url to build page
+  def merge_request_page(iid, sha, ref)
+    commit_page(sha, ref)
+  end
+
+  def commit_page(sha, ref)
+    build_page(sha, ref)
+  end
+
+  # Return complete url to merge_request page
   #
   # Ex.
   #   http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c
@@ -45,10 +57,27 @@ class CiService < Service
   #
   #
   # Ex.
-  #   @service.commit_status('13be4ac')
+  #   @service.merge_request_status(9, '13be4ac', 'dev')
+  #   # => 'success'
+  #
+  #   @service.merge_request_status(10, '2abe4ac', 'dev)
+  #   # => 'running'
+  #
+  #
+  def merge_request_status(iid, sha, ref)
+    commit_status(sha, ref)
+  end
+
+  # Return string with build status or :error symbol
+  #
+  # Allowed states: 'success', 'failed', 'running', 'pending', 'skipped'
+  #
+  #
+  # Ex.
+  #   @service.commit_status('13be4ac', 'master')
   #   # => 'success'
   #
-  #   @service.commit_status('2abe4ac')
+  #   @service.commit_status('2abe4ac', 'dev')
   #   # => 'running'
   #
   #
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..39f732c8113e0a03974c0fa8a8cd4f162499b4d3
--- /dev/null
+++ b/app/models/project_services/drone_ci_service.rb
@@ -0,0 +1,170 @@
+class DroneCiService < CiService
+  
+  prop_accessor :drone_url, :token, :enable_ssl_verification
+  validates :drone_url,
+    presence: true,
+    format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
+  validates :token,
+    presence: true,
+    format: { with: /\A([A-Za-z0-9]+)\z/ },  if: :activated?
+
+  after_save :compose_service_hook, if: :activated?
+
+  def compose_service_hook
+    hook = service_hook || build_service_hook
+    hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join
+    hook.enable_ssl_verification = enable_ssl_verification
+    hook.save
+  end
+
+  def execute(data)
+    case data[:object_kind]
+    when 'push'
+      service_hook.execute(data) if push_valid?(data)
+    when 'merge_request'
+      service_hook.execute(data) if merge_request_valid?(data)
+    when 'tag_push'
+      service_hook.execute(data) if tag_push_valid?(data)
+    end
+  end
+
+  def allow_target_ci?
+    true
+  end
+
+  def supported_events
+    %w(push merge_request tag_push)
+  end
+
+  def merge_request_status_path(iid, sha = nil, ref = nil)
+    url = [drone_url, 
+           "gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}", 
+           "?access_token=#{token}"]
+
+    URI.join(*url).to_s
+  end
+
+  def commit_status_path(sha, ref)
+    url = [drone_url, 
+           "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}", 
+           "?branch=#{URI::encode(ref.to_s)}&access_token=#{token}"]
+
+    URI.join(*url).to_s
+  end
+
+  def merge_request_status(iid, sha, ref)
+    response = HTTParty.get(merge_request_status_path(iid), verify: enable_ssl_verification)
+
+    if response.code == 200 and response['status']
+      case response['status']
+      when 'killed'
+        :canceled
+      when 'failure', 'error'
+        # Because drone return error if some test env failed
+        :failed
+      else
+        response["status"]
+      end
+    else
+      :error
+    end
+  rescue Errno::ECONNREFUSED
+    :error
+  end
+
+  def commit_status(sha, ref)
+    response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification)
+
+    if response.code == 200 and response['status']
+      case response['status']
+      when 'killed'
+        :canceled
+      when 'failure', 'error'
+        # Because drone return error if some test env failed
+        :failed
+      else
+        response["status"]
+      end
+    else
+      :error
+    end
+  rescue Errno::ECONNREFUSED
+    :error
+  end
+
+  def merge_request_page(iid, sha, ref)
+    url = [drone_url, 
+           "gitlab/#{project.namespace.path}/#{project.path}/redirect/pulls/#{iid}"]
+
+    URI.join(*url).to_s
+  end
+
+  def commit_page(sha, ref)
+    url = [drone_url, 
+           "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}", 
+           "?branch=#{URI::encode(ref.to_s)}"]
+
+    URI.join(*url).to_s
+  end
+
+  def commit_coverage(sha, ref)
+    nil
+  end
+
+  def build_page(sha, ref)
+    commit_page(sha, ref)
+  end
+
+  def builds_path
+    url = [drone_url, "#{project.namespace.path}/#{project.path}"]
+
+    URI.join(*url).to_s
+  end
+
+  def status_img_path
+    url = [drone_url, 
+           "api/badges/#{project.namespace.path}/#{project.path}/status.svg", 
+           "?branch=#{URI::encode(project.default_branch)}"]
+
+    URI.join(*url).to_s
+  end
+
+  def title
+    'Drone CI'
+  end
+
+  def description
+    'Drone is a Continuous Integration platform built on Docker, written in Go'
+  end
+
+  def to_param
+    'drone_ci'
+  end
+
+  def fields
+    [
+      { type: 'text', name: 'token', placeholder: 'Drone CI project specific token' },
+      { type: 'text', name: 'drone_url', placeholder: 'http://drone.example.com' },
+      { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" }
+    ]
+  end
+
+  private
+
+  def tag_push_valid?(data)
+    data[:total_commits_count] > 0 && !Gitlab::Git.blank_ref?(data[:after])
+  end
+
+  def push_valid?(data)
+    opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id, 
+                                                                source_branch: Gitlab::Git.ref_name(data[:ref]))
+
+    opened_merge_requests.empty? && data[:total_commits_count] > 0 && 
+      !Gitlab::Git.blank_ref?(data[:after])
+  end
+
+  def merge_request_valid?(data)
+    ['opened', 'reopened'].include?(data[:object_attributes][:state]) &&
+      data[:object_attributes][:merge_status] == 'unchecked'
+  end
+end
diff --git a/app/models/service.rb b/app/models/service.rb
index dcef2866c3b347982371ab3fdb3f2530369277e3..60fcc9d2857a50dfdff4a9cddc51ab62b1d3b3fc 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -135,6 +135,7 @@ class Service < ActiveRecord::Base
       buildkite
       campfire
       custom_issue_tracker
+      drone_ci
       emails_on_push
       external_wiki
       flowdock
diff --git a/doc/api/services.md b/doc/api/services.md
index cbf767d1b251a773520f158229bdb1e21e0c7e22..bc5049dd3026e1501e62eca92bfe3a798e87f9a8 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -1,8 +1,296 @@
 # Services
 
+## Asana
+
+Asana - Teamwork without email
+
+### Create/Edit Asana service
+
+Set Asana service for a project.
+
+> This service adds commit messages as comments to Asana tasks. Once enabled, commit messages are checked for Asana task URLs (for example, `https://app.asana.com/0/123456/987654`) or task IDs starting with # (for example, `#987654`). Every task ID found will get the commit comment added to it.  You can also close a task with a message containing: `fix #123456`.  You can find your Api Keys here: http://developer.asana.com/documentation/#api_keys
+
+```
+PUT /projects/:id/services/asana
+```
+
+Parameters:
+
+- `api_key` (**required**) - User API token. User must have access to task,all comments will be attributed to this user.
+- `restrict_to_branch` (optional) - Comma-separated list of branches which will beautomatically inspected. Leave blank to include all branches.
+
+### Delete Asana service
+
+Delete Asana service for a project.
+
+```
+DELETE /projects/:id/services/asana
+```
+
+## Assembla
+
+Project Management Software (Source Commits Endpoint)
+
+### Create/Edit Assembla service
+
+Set Assembla service for a project.
+
+```
+PUT /projects/:id/services/assembla
+```
+
+Parameters:
+
+- `token` (**required**)
+- `subdomain` (optional)
+
+### Delete Assembla service
+
+Delete Assembla service for a project.
+
+```
+DELETE /projects/:id/services/assembla
+```
+
+## Atlassian Bamboo CI
+
+A continuous integration and build server
+
+### Create/Edit Atlassian Bamboo CI service
+
+Set Atlassian Bamboo CI service for a project.
+
+> You must set up automatic revision labeling and a repository trigger in Bamboo.
+
+```
+PUT /projects/:id/services/bamboo
+```
+
+Parameters:
+
+- `bamboo_url` (**required**) - Bamboo root URL like https://bamboo.example.com
+- `build_key` (**required**) - Bamboo build plan key like KEY
+- `username` (**required**) - A user with API access, if applicable
+- `password` (**required**)
+
+### Delete Atlassian Bamboo CI service
+
+Delete Atlassian Bamboo CI service for a project.
+
+```
+DELETE /projects/:id/services/bamboo
+```
+
+## Buildkite
+
+Continuous integration and deployments
+
+### Create/Edit Buildkite service
+
+Set Buildkite service for a project.
+
+```
+PUT /projects/:id/services/buildkite
+```
+
+Parameters:
+
+- `token` (**required**) - Buildkite project GitLab token
+- `project_url` (**required**) - https://buildkite.com/example/project
+- `enable_ssl_verification` (optional) - Enable SSL verification
+
+### Delete Buildkite service
+
+Delete Buildkite service for a project.
+
+```
+DELETE /projects/:id/services/buildkite
+```
+
+## Campfire
+
+Simple web-based real-time group chat
+
+### Create/Edit Campfire service
+
+Set Campfire service for a project.
+
+```
+PUT /projects/:id/services/campfire
+```
+
+Parameters:
+
+- `token` (**required**)
+- `subdomain` (optional)
+- `room` (optional)
+
+### Delete Campfire service
+
+Delete Campfire service for a project.
+
+```
+DELETE /projects/:id/services/campfire
+```
+
+## Custom Issue Tracker
+
+Custom issue tracker
+
+### Create/Edit Custom Issue Tracker service
+
+Set Custom Issue Tracker service for a project.
+
+```
+PUT /projects/:id/services/custom-issue-tracker
+```
+
+Parameters:
+
+- `new_issue_url` (**required**) - New Issue url
+- `issues_url` (**required**) - Issue url
+- `project_url` (**required**) - Project url
+- `description` (optional) - Custom issue tracker
+- `title` (optional) - Custom Issue Tracker
+
+### Delete Custom Issue Tracker service
+
+Delete Custom Issue Tracker service for a project.
+
+```
+DELETE /projects/:id/services/custom-issue-tracker
+```
+
+## Drone CI
+
+Drone is a Continuous Integration platform built on Docker, written in Go
+
+### Create/Edit Drone CI service
+
+Set Drone CI service for a project.
+
+```
+PUT /projects/:id/services/drone-ci
+```
+
+Parameters:
+
+- `token` (**required**) - Drone CI project specific token
+- `drone_url` (**required**) - http://drone.example.com
+- `enable_ssl_verification` (optional) - Enable SSL verification
+
+### Delete Drone CI service
+
+Delete Drone CI service for a project.
+
+```
+DELETE /projects/:id/services/drone-ci
+```
+
+## Emails on push
+
+Email the commits and diff of each push to a list of recipients.
+
+### Create/Edit Emails on push service
+
+Set Emails on push service for a project.
+
+```
+PUT /projects/:id/services/emails-on-push
+```
+
+Parameters:
+
+- `recipients` (**required**) - Emails separated by whitespace
+- `disable_diffs` (optional) - Disable code diffs
+- `send_from_committer_email` (optional) - Send from committer
+
+### Delete Emails on push service
+
+Delete Emails on push service for a project.
+
+```
+DELETE /projects/:id/services/emails-on-push
+```
+
+## External Wiki
+
+Replaces the link to the internal wiki with a link to an external wiki.
+
+### Create/Edit External Wiki service
+
+Set External Wiki service for a project.
+
+```
+PUT /projects/:id/services/external-wiki
+```
+
+Parameters:
+
+- `external_wiki_url` (**required**) - The URL of the external Wiki
+
+### Delete External Wiki service
+
+Delete External Wiki service for a project.
+
+```
+DELETE /projects/:id/services/external-wiki
+```
+
+## Flowdock
+
+Flowdock is a collaboration web app for technical teams.
+
+### Create/Edit Flowdock service
+
+Set Flowdock service for a project.
+
+```
+PUT /projects/:id/services/flowdock
+```
+
+Parameters:
+
+- `token` (**required**) - Flowdock Git source token
+
+### Delete Flowdock service
+
+Delete Flowdock service for a project.
+
+```
+DELETE /projects/:id/services/flowdock
+```
+
+## Gemnasium
+
+Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.
+
+### Create/Edit Gemnasium service
+
+Set Gemnasium service for a project.
+
+```
+PUT /projects/:id/services/gemnasium
+```
+
+Parameters:
+
+- `api_key` (**required**) - Your personal API KEY on gemnasium.com 
+- `token` (**required**) - The project's slug on gemnasium.com
+
+### Delete Gemnasium service
+
+Delete Gemnasium service for a project.
+
+```
+DELETE /projects/:id/services/gemnasium
+```
+
 ## GitLab CI
 
-### Edit GitLab CI service
+Continuous integration server from GitLab
+
+### Create/Edit GitLab CI service
 
 Set GitLab CI service for a project.
 
@@ -12,12 +300,13 @@ PUT /projects/:id/services/gitlab-ci
 
 Parameters:
 
-- `token` (required) - CI project token
-- `project_url` (required) - CI project URL
+- `token` (**required**) - GitLab CI project specific token
+- `project_url` (**required**) - http://ci.gitlabhq.com/projects/3
+- `enable_ssl_verification` (optional) - Enable SSL verification
 
 ### Delete GitLab CI service
 
-Delete GitLab CI service settings for a project.
+Delete GitLab CI service for a project.
 
 ```
 DELETE /projects/:id/services/gitlab-ci
@@ -25,17 +314,24 @@ DELETE /projects/:id/services/gitlab-ci
 
 ## HipChat
 
-### Edit HipChat service
+Private group chat and IM
+
+### Create/Edit HipChat service
 
-Set HipChat service for project.
+Set HipChat service for a project.
 
 ```
 PUT /projects/:id/services/hipchat
 ```
+
 Parameters:
 
-- `token` (required) - HipChat token
-- `room` (required) - HipChat room name
+- `token` (**required**) - Room token
+- `color` (optional)
+- `notify` (optional)
+- `room` (optional) - Room name or ID
+- `api_version` (optional) - Leave blank for default (v2)
+- `server` (optional) - Leave blank for default. https://hipchat.example.com
 
 ### Delete HipChat service
 
@@ -44,3 +340,197 @@ Delete HipChat service for a project.
 ```
 DELETE /projects/:id/services/hipchat
 ```
+
+## Irker (IRC gateway)
+
+Send IRC messages, on update, to a list of recipients through an Irker gateway.
+
+### Create/Edit Irker (IRC gateway) service
+
+Set Irker (IRC gateway) service for a project.
+
+>  NOTE: Irker does NOT have built-in authentication, which makes it vulnerable to spamming IRC channels if it is hosted outside of a  firewall. Please make sure you run the daemon within a secured network  to prevent abuse. For more details, read: http://www.catb.org/~esr/irker/security.html.
+
+```
+PUT /projects/:id/services/irker
+```
+
+Parameters:
+
+- `recipients` (**required**) - Recipients/channels separated by whitespaces
+- `default_irc_uri` (optional) - irc://irc.network.net:6697/
+- `server_port` (optional) - 6659
+- `server_host` (optional) - localhost
+- `colorize_messages` (optional)
+
+### Delete Irker (IRC gateway) service
+
+Delete Irker (IRC gateway) service for a project.
+
+```
+DELETE /projects/:id/services/irker
+```
+
+## JIRA
+
+Jira issue tracker
+
+### Create/Edit JIRA service
+
+Set JIRA service for a project.
+
+> Setting `project_url`, `issues_url` and `new_issue_url` will allow a user to easily navigate to the Jira issue tracker. See the [integration doc](http://doc.gitlab.com/ce/integration/external-issue-tracker.html) for details.  Support for referencing commits and automatic closing of Jira issues directly from GitLab is [available in GitLab EE.](http://doc.gitlab.com/ee/integration/jira.html)
+
+```
+PUT /projects/:id/services/jira
+```
+
+Parameters:
+
+- `new_issue_url` (**required**) - New Issue url
+- `project_url` (**required**) - Project url
+- `issues_url` (**required**) - Issue url
+- `description` (optional) - Jira issue tracker
+
+### Delete JIRA service
+
+Delete JIRA service for a project.
+
+```
+DELETE /projects/:id/services/jira
+```
+
+## PivotalTracker
+
+Project Management Software (Source Commits Endpoint)
+
+### Create/Edit PivotalTracker service
+
+Set PivotalTracker service for a project.
+
+```
+PUT /projects/:id/services/pivotaltracker
+```
+
+Parameters:
+
+- `token` (**required**)
+
+### Delete PivotalTracker service
+
+Delete PivotalTracker service for a project.
+
+```
+DELETE /projects/:id/services/pivotaltracker
+```
+
+## Pushover
+
+Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.
+
+### Create/Edit Pushover service
+
+Set Pushover service for a project.
+
+```
+PUT /projects/:id/services/pushover
+```
+
+Parameters:
+
+- `api_key` (**required**) - Your application key
+- `user_key` (**required**) - Your user key
+- `priority` (**required**)
+- `device` (optional) - Leave blank for all active devices
+- `sound` (optional)
+
+### Delete Pushover service
+
+Delete Pushover service for a project.
+
+```
+DELETE /projects/:id/services/pushover
+```
+
+## Redmine
+
+Redmine issue tracker
+
+### Create/Edit Redmine service
+
+Set Redmine service for a project.
+
+```
+PUT /projects/:id/services/redmine
+```
+
+Parameters:
+
+- `new_issue_url` (**required**) - New Issue url
+- `project_url` (**required**) - Project url
+- `issues_url` (**required**) - Issue url
+- `description` (optional) - Redmine issue tracker
+
+### Delete Redmine service
+
+Delete Redmine service for a project.
+
+```
+DELETE /projects/:id/services/redmine
+```
+
+## Slack
+
+A team communication tool for the 21st century
+
+### Create/Edit Slack service
+
+Set Slack service for a project.
+
+```
+PUT /projects/:id/services/slack
+```
+
+Parameters:
+
+- `webhook` (**required**) - https://hooks.slack.com/services/...
+- `username` (optional) - username
+- `channel` (optional) - #channel
+
+### Delete Slack service
+
+Delete Slack service for a project.
+
+```
+DELETE /projects/:id/services/slack
+```
+
+## JetBrains TeamCity CI
+
+A continuous integration and build server
+
+### Create/Edit JetBrains TeamCity CI service
+
+Set JetBrains TeamCity CI service for a project.
+
+> The build configuration in Teamcity must use the build format number %build.vcs.number% you will also want to configure monitoring of all branches so merge requests build, that setting is in the vsc root advanced settings.
+
+```
+PUT /projects/:id/services/teamcity
+```
+
+Parameters:
+
+- `teamcity_url` (**required**) - TeamCity root URL like https://teamcity.example.com
+- `build_type` (**required**) - Build configuration ID
+- `username` (**required**) - A user with permissions to trigger a manual build
+- `password` (**required**)
+
+### Delete JetBrains TeamCity CI service
+
+Delete JetBrains TeamCity CI service for a project.
+
+```
+DELETE /projects/:id/services/teamcity
+```
+
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 73717ffc7d68a1622942b4576b13deeaf53f0e99..09400d9b1633289e7156108abb7935d3590d44f6 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -279,6 +279,7 @@ X-Gitlab-Event: Note Hook
       "name": "Gitlab Test",
       "ssh_url": "git@example.com:gitlab-org/gitlab-test.git",
       "http_url": "http://example.com/gitlab-org/gitlab-test.git",
+      "web_url": "http://example.com/gitlab-org/gitlab-test",
       "namespace": "Gitlab Org",
       "visibility_level": 10
     },
@@ -286,6 +287,7 @@ X-Gitlab-Event: Note Hook
       "name": "Gitlab Test",
       "ssh_url": "git@example.com:gitlab-org/gitlab-test.git",
       "http_url": "http://example.com/gitlab-org/gitlab-test.git",
+      "web_url": "http://example.com/gitlab-org/gitlab-test",
       "namespace": "Gitlab Org",
       "visibility_level": 10
     },
@@ -462,6 +464,7 @@ X-Gitlab-Event: Merge Request Hook
       "name": "awesome_project",
       "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git",
       "http_url": "http://example.com/awesome_space/awesome_project.git",
+      "web_url": "http://example.com/awesome_space/awesome_project",
       "visibility_level": 20,
       "namespace": "awesome_space"
     },
@@ -469,6 +472,7 @@ X-Gitlab-Event: Merge Request Hook
       "name": "awesome_project",
       "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git",
       "http_url": "http://example.com/awesome_space/awesome_project.git",
+      "web_url": "http://example.com/awesome_space/awesome_project",
       "visibility_level": 20,
       "namespace": "awesome_space"
     },
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 1ebf9a1f022d6e2000e2c9924354914479664260..76c9cc2e3a42ea3ae7ba4ebe9b862e5b408d0862 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -55,6 +55,32 @@ module API
       end
     end
 
+    def project_service
+      @project_service ||= begin
+        underscored_service = params[:service_slug].underscore
+
+        if Service.available_services_names.include?(underscored_service)
+          user_project.build_missing_services
+
+          service_method = "#{underscored_service}_service"
+          
+          send_service(service_method)
+        end
+      end
+   
+      @project_service || not_found!("Service")
+    end
+
+    def send_service(service_method)
+      user_project.send(service_method)
+    end
+
+    def service_attributes
+      @service_attributes ||= project_service.fields.inject([]) do |arr, hash|
+        arr << hash[:name].to_sym
+      end
+    end
+
     def find_group(id)
       begin
         group = Group.find(id)
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 3ad59cf3adf6ab95629b8fed1aa175f26efafeca..73645cedea4395a682c0c8f6f846b50302467d1c 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -4,73 +4,49 @@ module API
     before { authenticate! }
     before { authorize_admin_project }
 
+
     resource :projects do
-      # Set GitLab CI service for project
-      #
-      # Parameters:
-      #   token (required) - CI project token
-      #   project_url (required) - CI project url
+      # Set <service_slug> service for project
       #
       # Example Request:
+      #
       #   PUT /projects/:id/services/gitlab-ci
-      put ":id/services/gitlab-ci" do
-        required_attributes! [:token, :project_url]
-        attrs = attributes_for_keys [:token, :project_url]
-        user_project.build_missing_services
-
-        if user_project.gitlab_ci_service.update_attributes(attrs.merge(active: true))
-          true
-        else
-          not_found!
-        end
-      end
-
-      # Delete GitLab CI service settings
       #
-      # Example Request:
-      #   DELETE /projects/:id/services/gitlab-ci
-      delete ":id/services/gitlab-ci" do
-        if user_project.gitlab_ci_service
-          user_project.gitlab_ci_service.update_attributes(
-            active: false,
-            token: nil,
-            project_url: nil
-          )
-        end
-      end
+      put ':id/services/:service_slug' do
+        if project_service
+          validators = project_service.class.validators.select do |s|
+            s.class == ActiveRecord::Validations::PresenceValidator &&
+              s.attributes != [:project_id]
+          end
 
-      # Set Hipchat service for project
-      #
-      # Parameters:
-      #   token (required) - Hipchat token
-      #   room (required) - Hipchat room name
-      #
-      # Example Request:
-      #   PUT /projects/:id/services/hipchat
-      put ':id/services/hipchat' do
-        required_attributes! [:token, :room]
-        attrs = attributes_for_keys [:token, :room]
-        user_project.build_missing_services
+          required_attributes! validators.map(&:attributes).flatten.uniq
+          attrs = attributes_for_keys service_attributes 
 
-        if user_project.hipchat_service.update_attributes(
-            attrs.merge(active: true))
-          true
-        else
-          not_found!
+          if project_service.update_attributes(attrs.merge(active: true))
+            true
+          else
+            not_found!
+          end
         end
       end
 
-      # Delete Hipchat service settings
+      # Delete <service_slug> service for project
       #
       # Example Request:
-      #   DELETE /projects/:id/services/hipchat
-      delete ':id/services/hipchat' do
-        if user_project.hipchat_service
-          user_project.hipchat_service.update_attributes(
-            active: false,
-            token: nil,
-            room: nil
-          )
+      #
+      #   DELETE /project/:id/services/gitlab-ci
+      #
+      delete ':id/services/:service_slug' do
+        if project_service
+          attrs = service_attributes.inject({}) do |hash, key|
+            hash.merge!(key => nil)
+          end
+          
+          if project_service.update_attributes(attrs.merge(active: false))
+            true
+          else
+            not_found!
+          end
         end
       end
     end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index dc87aa52a3e0fb41720f4fcacdb20e0d34fd9f09..aa46c9a6d492a7a4fbc6135682e68697fa9fccc3 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -10,7 +10,7 @@ module Grack
       @request = Rack::Request.new(env)
       @auth = Request.new(env)
 
-      @gitlab_ci = false
+      @ci = false
 
       # Need this patch due to the rails mount
       # Need this if under RELATIVE_URL_ROOT
@@ -28,7 +28,7 @@ module Grack
       if project && authorized_request?
         # Tell gitlab-git-http-server the request is OK, and what the GL_ID is
         render_grack_auth_ok
-      elsif @user.nil? && !@gitlab_ci
+      elsif @user.nil? && !@ci
         unauthorized
       else
         render_not_found
@@ -47,8 +47,8 @@ module Grack
 
       # Allow authentication for GitLab CI service
       # if valid token passed
-      if gitlab_ci_request?(login, password)
-        @gitlab_ci = true
+      if ci_request?(login, password)
+        @ci = true
         return
       end
 
@@ -60,12 +60,17 @@ module Grack
       end
     end
 
-    def gitlab_ci_request?(login, password)
-      if login == "gitlab-ci-token" && project && project.gitlab_ci?
-        token = project.gitlab_ci_service.token
+    def ci_request?(login, password)
+      matched_login = /(?<s>^[a-zA-Z]*-ci)-token$/.match(login)
 
-        if token.present? && token == password && git_cmd == 'git-upload-pack'
-          return true
+      if project && matched_login.present? && git_cmd == 'git-upload-pack'
+        underscored_service = matched_login['s'].underscore 
+
+        if Service.available_services_names.include?(underscored_service)
+          service_method = "#{underscored_service}_service"
+          service = project.send(service_method)
+
+          return service && service.activated? && service.valid_token?(password)
         end
       end
 
@@ -124,7 +129,7 @@ module Grack
     end
 
     def authorized_request?
-      return true if @gitlab_ci
+      return true if @ci
 
       case git_cmd
       when *Gitlab::GitAccess::DOWNLOAD_COMMANDS
diff --git a/lib/tasks/services.rake b/lib/tasks/services.rake
new file mode 100644
index 0000000000000000000000000000000000000000..53d912d2a7c0fb79b89c426874834f579f5c5995
--- /dev/null
+++ b/lib/tasks/services.rake
@@ -0,0 +1,89 @@
+services_template = <<-ERB
+# Services
+
+<% services.each do |service| %>
+## <%= service[:title] %>
+
+
+<% unless service[:description].blank? %>
+<%= service[:description] %>
+<% end %>
+
+
+### Create/Edit <%= service[:title] %> service
+
+Set <%= service[:title] %> service for a project.
+<% unless service[:help].blank? %>
+
+> <%= service[:help].gsub("\n", ' ') %>
+
+<% end %>
+
+```
+PUT /projects/:id/services/<%= service[:dashed_name] %>
+
+```
+
+Parameters:
+
+<% service[:params].each do |param| %>
+- `<%= param[:name] %>` <%= param[:required] ? "(**required**)" : "(optional)"  %><%= [" -",  param[:description]].join(" ").gsub("\n", '') unless param[:description].blank? %>
+
+<% end %>
+
+### Delete <%= service[:title] %> service
+
+Delete <%= service[:title] %> service for a project.
+
+```
+DELETE /projects/:id/services/<%= service[:dashed_name] %>
+
+```
+
+<% end %>
+ERB
+
+namespace :services do
+  task :doc do
+    services = Service.available_services_names.map do |s|
+      service_start = Time.now
+      klass = "#{s}_service".classify.constantize
+     
+      service = klass.new
+
+      service_hash = {}
+
+      service_hash[:title] = service.title
+      service_hash[:dashed_name] = s.dasherize
+      service_hash[:description] = service.description
+      service_hash[:help] = service.help
+      service_hash[:params] = service.fields.map do |p|
+        param_hash = {}
+
+        param_hash[:name] = p[:name]
+        param_hash[:description] = p[:placeholder] || p[:title]
+        param_hash[:required] = klass.validators_on(p[:name].to_sym).any? do |v| 
+          v.class == ActiveRecord::Validations::PresenceValidator
+        end
+
+        param_hash
+      end.sort_by { |p| p[:required] ? 0 : 1 }
+
+      puts "Collected data for: #{service.title}, #{Time.now-service_start}"
+      service_hash
+    end
+
+    doc_start = Time.now
+    doc_path = File.join(Rails.root, 'doc', 'api', 'services.md')
+
+    result = ERB.new(services_template, 0 , '>')
+      .result(OpenStruct.new(services: services).instance_eval { binding })
+
+    File.open(doc_path, 'w') do |f|
+      f.write result
+    end
+
+    puts "write a new service.md to: #{doc_path.to_s}, #{Time.now-doc_start}"
+
+  end
+end
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bad9a9e6e1a89550ffa7a053741880f25613d80a
--- /dev/null
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -0,0 +1,107 @@
+# == Schema Information
+#
+# Table name: services
+#
+#  id                    :integer          not null, primary key
+#  type                  :string(255)
+#  title                 :string(255)
+#  project_id            :integer
+#  created_at            :datetime
+#  updated_at            :datetime
+#  active                :boolean          default(FALSE), not null
+#  properties            :text
+#  template              :boolean          default(FALSE)
+#  push_events           :boolean          default(TRUE)
+#  issues_events         :boolean          default(TRUE)
+#  merge_requests_events :boolean          default(TRUE)
+#  tag_push_events       :boolean          default(TRUE)
+#  note_events           :boolean          default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe DroneCiService do
+  describe 'associations' do
+    it { is_expected.to belong_to(:project) }
+    it { is_expected.to have_one(:service_hook) }
+  end
+
+  describe 'validations' do
+    context 'active' do
+      before { allow(subject).to receive(:activated?).and_return(true) }
+
+      it { is_expected.to validate_presence_of(:token) }
+      it { is_expected.to validate_presence_of(:drone_url) }
+      it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
+      it { is_expected.to allow_value('http://ci.example.com').for(:drone_url) }
+      it { is_expected.not_to allow_value('token with spaces').for(:token) }
+      it { is_expected.not_to allow_value('token/with%spaces').for(:token) }
+      it { is_expected.not_to allow_value('this is not url').for(:drone_url) }
+      it { is_expected.not_to allow_value('http//noturl').for(:drone_url) }
+      it { is_expected.not_to allow_value('ftp://ci.example.com').for(:drone_url) }
+    end
+
+    context 'inactive' do
+      before { allow(subject).to receive(:activated?).and_return(false) }
+
+      it { is_expected.not_to validate_presence_of(:token) }
+      it { is_expected.not_to validate_presence_of(:drone_url) }
+      it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
+      it { is_expected.to allow_value('http://drone.example.com').for(:drone_url) }
+      it { is_expected.to allow_value('token with spaces').for(:token) }
+      it { is_expected.to allow_value('ftp://drone.example.com').for(:drone_url) }
+    end
+  end
+
+  shared_context :drone_ci_service do
+    let(:drone)      { DroneCiService.new }
+    let(:project)    { create(:project, name: 'project') }
+    let(:path)       { "#{project.namespace.path}/#{project.path}" }
+    let(:drone_url)  { 'http://drone.example.com' }
+    let(:sha)        { '2ab7834c' }
+    let(:branch)     { 'dev' }
+    let(:token)      { 'secret' }
+    let(:iid)        { rand(1..9999) }
+
+    before(:each) do
+      allow(drone).to receive_messages(
+        project_id: project.id,
+        project: project,
+        active: true,
+        drone_url: drone_url,
+        token: token
+      )
+    end
+  end
+
+  describe "service page/path methods" do
+    include_context :drone_ci_service
+
+    # URL's
+    let(:commit_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" }
+    let(:merge_request_page) { "#{drone_url}/gitlab/#{path}/redirect/pulls/#{iid}" }
+    let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" }
+    let(:merge_request_status_path) { "#{drone_url}/gitlab/#{path}/pulls/#{iid}?access_token=#{token}" }
+
+    it { expect(drone.build_page(sha, branch)).to eq(commit_page) }
+    it { expect(drone.commit_page(sha, branch)).to eq(commit_page) }
+    it { expect(drone.merge_request_page(iid, sha, branch)).to eq(merge_request_page) }
+    it { expect(drone.commit_status_path(sha, branch)).to eq(commit_status_path) }
+    it { expect(drone.merge_request_status_path(iid, sha, branch)).to eq(merge_request_status_path)  }
+  end
+
+  describe "execute" do
+    include_context :drone_ci_service
+
+    let(:user)    { create(:user, username: 'username') }
+    let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+
+    it do
+      service_hook = double
+      expect(service_hook).to receive(:execute)
+      expect(drone).to receive(:service_hook).and_return(service_hook)
+
+      drone.execute(push_sample_data)
+    end
+  end
+end
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 6d29a28580a7a849d9686876ceca09c2a3bc1284..c297904614abc24693cfc9c57dbf788b9b6c1f4b 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -5,64 +5,47 @@ describe API::API, api: true  do
   let(:user) { create(:user) }
   let(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
 
-  describe "POST /projects/:id/services/gitlab-ci" do
-    it "should update gitlab-ci settings" do
-      put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'secrettoken', project_url: "http://ci.example.com/projects/1"
-
-      expect(response.status).to eq(200)
-    end
-
-    it "should return if required fields missing" do
-      put api("/projects/#{project.id}/services/gitlab-ci", user), project_url: "http://ci.example.com/projects/1", active: true
-
-      expect(response.status).to eq(400)
-    end
-
-    it "should return if the format of token is invalid" do
-      put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'token-with dashes and spaces%', project_url: "http://ci.example.com/projects/1", active: true
-
-      expect(response.status).to eq(404)
-    end
-
-    it "should return if the format of token is invalid" do
-      put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'token-with dashes and spaces%', project_url: "ftp://ci.example/projects/1", active: true
-
-      expect(response.status).to eq(404)
-    end
-  end
-
-  describe "DELETE /projects/:id/services/gitlab-ci" do
-    it "should update gitlab-ci settings" do
-      delete api("/projects/#{project.id}/services/gitlab-ci", user)
-
-      expect(response.status).to eq(200)
-      expect(project.gitlab_ci_service).to be_nil
-    end
-  end
-
-  describe 'PUT /projects/:id/services/hipchat' do
-    it 'should update hipchat settings' do
-      put api("/projects/#{project.id}/services/hipchat", user),
-          token: 'secret-token', room: 'test'
-
-      expect(response.status).to eq(200)
-      expect(project.hipchat_service).not_to be_nil
-    end
-
-    it 'should return if required fields missing' do
-      put api("/projects/#{project.id}/services/gitlab-ci", user),
-          token: 'secret-token', active: true
-
-      expect(response.status).to eq(400)
-    end
-  end
-
-  describe 'DELETE /projects/:id/services/hipchat' do
-    it 'should delete hipchat settings' do
-      delete api("/projects/#{project.id}/services/hipchat", user)
-
-      expect(response.status).to eq(200)
-      expect(project.hipchat_service).to be_nil
+  Service.available_services_names.each do |service|
+    describe "PUT /projects/:id/services/#{service.dasherize}" do
+      include_context service
+
+      it "should update #{service} settings" do
+        put api("/projects/#{project.id}/services/#{dashed_service}", user), service_attrs
+
+        expect(response.status).to eq(200)
+      end
+
+      it "should return if required fields missing" do
+        attrs = service_attrs
+        
+        required_attributes = service_attrs_list.select do |attr|
+          service_klass.validators_on(attr).any? do |v| 
+            v.class == ActiveRecord::Validations::PresenceValidator
+          end
+        end
+
+        if required_attributes.empty?
+          expected_code = 200
+        else
+          attrs.delete(required_attributes.shuffle.first)
+          expected_code = 400
+        end
+        
+        put api("/projects/#{project.id}/services/#{dashed_service}", user), attrs
+
+        expect(response.status).to eq(expected_code)
+      end
+    end
+
+    describe "DELETE /projects/:id/services/#{service.dasherize}" do
+      include_context service
+
+      it "should delete #{service}" do
+        delete api("/projects/#{project.id}/services/#{dashed_service}", user)
+
+        expect(response.status).to eq(200)
+        expect(project.send(service_method).activated?).to be_falsey
+      end
     end
   end
 end
diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4d007ae55ee1391874cb528172d67cb14b9d057a
--- /dev/null
+++ b/spec/support/services_shared_context.rb
@@ -0,0 +1,21 @@
+Service.available_services_names.each do |service|
+  shared_context service do
+    let(:dashed_service) { service.dasherize }
+    let(:service_method) { "#{service}_service".to_sym }
+    let(:service_klass) { "#{service}_service".classify.constantize }
+    let(:service_attrs_list) { service_klass.new.fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
+    let(:service_attrs) do
+      service_attrs_list.inject({}) do |hash, k|
+        if k =~ /^(token*|.*_token|.*_key)/
+          hash.merge!(k => 'secrettoken')
+        elsif k =~ /^(.*_url|url|webhook)/
+          hash.merge!(k => "http://example.com")
+        elsif service == 'irker' && k == :recipients
+          hash.merge!(k => 'irc://irc.network.net:666/#channel')
+        else
+          hash.merge!(k => "someword")
+        end
+      end
+    end
+  end
+end