diff --git a/reviewboard/hostingsvcs/bitbucket.py b/reviewboard/hostingsvcs/bitbucket.py
index 55f571314956f493f5b13ef89aa6c717d77af7bf..9a8d49e62e725efb4e8f1b3c78ecc97f43fa0401 100644
--- a/reviewboard/hostingsvcs/bitbucket.py
+++ b/reviewboard/hostingsvcs/bitbucket.py
@@ -6,21 +6,25 @@ from collections import defaultdict
 
 from django import forms
 from django.conf.urls import patterns, url
-from django.http import HttpResponse
+from django.http import HttpResponse, HttpResponseBadRequest
+from django.template import RequestContext
+from django.template.loader import render_to_string
 from django.utils.six.moves.urllib.error import HTTPError, URLError
 from django.utils.six.moves.urllib.parse import quote
 from django.utils.translation import ugettext_lazy as _
 from django.views.decorators.http import require_POST
 
-from reviewboard.admin.server import get_server_url
+from reviewboard.admin.server import build_server_url, get_server_url
 from reviewboard.hostingsvcs.errors import InvalidPlanError
 from reviewboard.hostingsvcs.forms import HostingServiceForm
 from reviewboard.hostingsvcs.hook_utils import (close_all_review_requests,
+                                                get_repository_for_hook,
                                                 get_review_request_id)
 from reviewboard.hostingsvcs.service import HostingService
 from reviewboard.scmtools.crypto_utils import (decrypt_password,
                                                encrypt_password)
 from reviewboard.scmtools.errors import FileNotFoundError
+from reviewboard.site.urlresolvers import local_site_reverse
 
 
 class BitbucketPersonalForm(HostingServiceForm):
@@ -61,10 +65,12 @@ class Bitbucket(HostingService):
     supports_repositories = True
     supports_bug_trackers = True
 
+    has_repository_hook_instructions = True
+
     repository_url_patterns = patterns(
         '',
 
-        url(r'^hooks/close-submitted/$',
+        url(r'^hooks/(?P<hooks_uuid>[a-z0-9]+)/close-submitted/$',
             'reviewboard.hostingsvcs.bitbucket'
             '.post_receive_hook_close_submitted',
             name='bitbucket-hooks-close-submitted'),
@@ -183,6 +189,31 @@ class Bitbucket(HostingService):
         except (URLError, HTTPError, FileNotFoundError):
             return False
 
+    def get_repository_hook_instructions(self, request, repository):
+        """Returns instructions for setting up incoming webhooks."""
+        webhook_endpoint_url = build_server_url(local_site_reverse(
+            'bitbucket-hooks-close-submitted',
+            local_site=repository.local_site,
+            kwargs={
+                'repository_id': repository.pk,
+                'hosting_service_id': repository.hosting_account.service_name,
+                'hooks_uuid': repository.get_or_create_hooks_uuid(),
+            }))
+        add_webhook_url = (
+            'https://bitbucket.org/%s/%s/admin/hooks?service=POST&url=%s'
+            % (self._get_repository_owner(repository),
+               self._get_repository_name(repository),
+               webhook_endpoint_url))
+
+
+        return render_to_string(
+            'hostingsvcs/bitbucket/repo_hook_instructions.html',
+            RequestContext(request, {
+                'repository': repository,
+                'server_url': get_server_url(),
+                'add_webhook_url': add_webhook_url,
+            }))
+
     def _api_get_repository(self, username, repo_name):
         url = self._build_api_url('repositories/%s/%s'
                                   % (username, repo_name))
@@ -271,16 +302,20 @@ class Bitbucket(HostingService):
 @require_POST
 def post_receive_hook_close_submitted(request, local_site_name=None,
                                       repository_id=None,
-                                      hosting_service_id=None):
+                                      hosting_service_id=None,
+                                      hooks_uuid=None):
     """Closes review requests as submitted automatically after a push."""
+    repository = get_repository_for_hook(repository_id, hosting_service_id,
+                                         local_site_name, hooks_uuid)
+
     if 'payload' not in request.POST:
-        return HttpResponse(status=400)
+        return HttpResponseBadRequest('Missing payload')
 
     try:
         payload = json.loads(request.POST['payload'])
     except ValueError as e:
         logging.error('The payload is not in JSON format: %s', e)
-        return HttpResponse(status=400)
+        return HttpResponseBadRequest('Invalid payload format')
 
     server_url = get_server_url(request=request)
     review_request_id_to_commits = \
@@ -288,7 +323,7 @@ def post_receive_hook_close_submitted(request, local_site_name=None,
 
     if review_request_id_to_commits:
         close_all_review_requests(review_request_id_to_commits,
-                                  local_site_name, repository_id,
+                                  local_site_name, repository,
                                   hosting_service_id)
 
     return HttpResponse()
diff --git a/reviewboard/hostingsvcs/hook_utils.py b/reviewboard/hostingsvcs/hook_utils.py
index f3321b48c3970b9e83890ea9813526eb1949d07f..7721728a9d93011c77848650ccd73b0b3b15ff20 100644
--- a/reviewboard/hostingsvcs/hook_utils.py
+++ b/reviewboard/hostingsvcs/hook_utils.py
@@ -22,11 +22,14 @@ def get_git_branch_name(ref_name):
 
 
 def get_repository_for_hook(repository_id, hosting_service_id,
-                            local_site_name):
+                            local_site_name, hooks_uuid=None):
     """Returns a Repository for the given hook parameters."""
     q = (Q(pk=repository_id) &
          Q(hosting_account__service_name=hosting_service_id))
 
+    if hooks_uuid:
+        q &= Q(hooks_uuid=hooks_uuid)
+
     if local_site_name:
         q &= Q(local_site__name=local_site_name)
     else:
diff --git a/reviewboard/hostingsvcs/tests.py b/reviewboard/hostingsvcs/tests.py
index b8a1966b621313fd97fa39ab05f5aa95507d2709..24096b3f21303c9475a783ccc7ab60a1327aa9f4 100644
--- a/reviewboard/hostingsvcs/tests.py
+++ b/reviewboard/hostingsvcs/tests.py
@@ -549,19 +549,26 @@ class BitbucketTests(ServiceTests):
             kwargs={
                 'repository_id': repository.pk,
                 'hosting_service_id': 'bitbucket',
+                'hooks_uuid': repository.get_or_create_hooks_uuid(),
             })
 
-        self._post_commit_hook_payload(url, review_request)
+        response = self._post_commit_hook_payload(url, review_request)
+        self.assertEqual(response.status_code, 404)
 
         review_request = ReviewRequest.objects.get(pk=review_request.pk)
         self.assertTrue(review_request.public)
         self.assertEqual(review_request.status, review_request.PENDING_REVIEW)
         self.assertEqual(review_request.changedescs.count(), 0)
 
-    @add_fixtures(['test_users', 'test_scmtools'])
+    @add_fixtures(['test_site', 'test_users', 'test_scmtools'])
     def test_close_submitted_hook_with_invalid_site(self):
         """Testing BitBucket close_submitted hook with invalid Local Site"""
-        repository = self.create_repository()
+        local_site = LocalSite.objects.get(name=self.local_site_name)
+        account = self._get_hosting_account(local_site=local_site)
+        account.save()
+
+        repository = self.create_repository(hosting_account=account,
+                                            local_site=local_site)
 
         review_request = self.create_review_request(repository=repository,
                                                     publish=True)
@@ -574,9 +581,11 @@ class BitbucketTests(ServiceTests):
             kwargs={
                 'repository_id': repository.pk,
                 'hosting_service_id': 'bitbucket',
+                'hooks_uuid': repository.get_or_create_hooks_uuid(),
             })
 
-        self._post_commit_hook_payload(url, review_request)
+        response = self._post_commit_hook_payload(url, review_request)
+        self.assertEqual(response.status_code, 404)
 
         review_request = ReviewRequest.objects.get(pk=review_request.pk)
         self.assertTrue(review_request.public)
@@ -588,22 +597,27 @@ class BitbucketTests(ServiceTests):
         """Testing BitBucket close_submitted hook with invalid hosting
         service ID
         """
-        repository = self.create_repository()
+        # We'll test against GitHub for this test.
+        account = self._get_hosting_account()
+        account.service_name = 'github'
+        account.save()
+        repository = self.create_repository(hosting_account=account)
 
         review_request = self.create_review_request(repository=repository,
                                                     publish=True)
         self.assertTrue(review_request.public)
         self.assertEqual(review_request.status, review_request.PENDING_REVIEW)
 
-        # We'll test against GitHub's hooks for this test.
         url = local_site_reverse(
-            'github-hooks-close-submitted',
+            'bitbucket-hooks-close-submitted',
             kwargs={
                 'repository_id': repository.pk,
-                'hosting_service_id': 'github',
+                'hosting_service_id': 'bitbucket',
+                'hooks_uuid': repository.get_or_create_hooks_uuid(),
             })
 
-        self._post_commit_hook_payload(url, review_request)
+        response = self._post_commit_hook_payload(url, review_request)
+        self.assertEqual(response.status_code, 404)
 
         review_request = ReviewRequest.objects.get(pk=review_request.pk)
         self.assertTrue(review_request.public)
@@ -629,6 +643,7 @@ class BitbucketTests(ServiceTests):
             kwargs={
                 'repository_id': repository.pk,
                 'hosting_service_id': 'bitbucket',
+                'hooks_uuid': repository.get_or_create_hooks_uuid(),
             })
 
         self._post_commit_hook_payload(url, review_request)
@@ -642,7 +657,7 @@ class BitbucketTests(ServiceTests):
         self.assertEqual(changedesc.text, 'Pushed to master (1c44b46)')
 
     def _post_commit_hook_payload(self, url, review_request):
-        self.client.post(
+        return self.client.post(
             url,
             data={
                 'payload': json.dumps({
@@ -2023,6 +2038,7 @@ class GoogleCodeTests(ServiceTests):
             kwargs={
                 'repository_id': repository.pk,
                 'hosting_service_id': 'bitbucket',
+                'hooks_uuid': repository.get_or_create_hooks_uuid(),
             })
 
         self._post_commit_hook_payload(url, review_request)
diff --git a/reviewboard/templates/hostingsvcs/bitbucket/repo_hook_instructions.html b/reviewboard/templates/hostingsvcs/bitbucket/repo_hook_instructions.html
new file mode 100644
index 0000000000000000000000000000000000000000..c0a9179e6eae8afc5e1a0d57e1ecaec70a640ea8
--- /dev/null
+++ b/reviewboard/templates/hostingsvcs/bitbucket/repo_hook_instructions.html
@@ -0,0 +1,21 @@
+{% load i18n %}
+
+<p>
+{% blocktrans %}
+ Review Board supports closing review requests that are referenced in
+ commit messages for any changes pushed to this repository. These references
+ are in the form of:
+{% endblocktrans %}
+</p>
+<pre>Reviewed at {{server_url}}r/123/</pre>
+<p>
+ Or:
+</p>
+<pre>Review Request #123</pre>
+<p>
+{% blocktrans %}
+ To configure this, simply
+ <a href="{{add_webhook_url}}" target="_blank">click here</a>
+ to add the new webhook. Then click <b>Save</b>. You're done!
+{% endblocktrans %}
+</p>
