Repository: readthedocs/readthedocs.org
Issue: Integration resync webhook fails

Steps to reproduce it:

1. Import GitLab project (do not use Manual Import)
1. Webhook are created sucessfully
1. Go to your GitLab project and remove the RTD integration from settings
1. Go to your Admin -> Integrations
1. Select your integration
1. Click on Resync

Message received:

> Webhook activation failed. Make sure you have the necessary permissions.

The logs show:

```
[10/Apr/2018 12:52:17] readthedocs.oauth.services.gitlab:347[30538]: DEBUG GitLab webhook update failure response: {'message': '404 Not found'}
```

This is happening because we are trying to _update_ a webhook in GitLab that it was manually removed by the user. So, we need to handle this case here and re-create the webhook if it fails when trying to update.

> NOTE: you need ngrok setup to test this in your local instance

Related: #3587 #2561 

Changed files:
readthedocs/oauth/services/bitbucket.py, readthedocs/oauth/services/gitlab.py, readthedocs/oauth/services/github.py

diff --git a/readthedocs/oauth/services/bitbucket.py b/readthedocs/oauth/services/bitbucket.py
index b14d1de84..1d93b9959 100644
--- a/readthedocs/oauth/services/bitbucket.py
+++ b/readthedocs/oauth/services/bitbucket.py
@@ -274,6 +274,12 @@ class BitbucketService(Service):
                     project,
                 )
                 return (True, resp)
+
+            # Bitbucket returns 404 when the webhook doesn't exist. In this
+            # case, we call ``setup_webhook`` to re-configure it from scratch
+            if resp.status_code == 404:
+                return self.setup_webhook(project)
+
         # Catch exceptions with request or deserializing JSON
         except (KeyError, RequestException, ValueError):
             log.exception(
diff --git a/readthedocs/oauth/services/github.py b/readthedocs/oauth/services/github.py
index 609332bf4..654adca4d 100644
--- a/readthedocs/oauth/services/github.py
+++ b/readthedocs/oauth/services/github.py
@@ -261,6 +261,12 @@ class GitHubService(Service):
                     project,
                 )
                 return (True, resp)
+
+            # GitHub returns 404 when the webhook doesn't exist. In this case,
+            # we call ``setup_webhook`` to re-configure it from scratch
+            if resp.status_code == 404:
+                return self.setup_webhook(project)
+
         # Catch exceptions with request or deserializing JSON
         except (RequestException, ValueError):
             log.exception(
diff --git a/readthedocs/oauth/services/gitlab.py b/readthedocs/oauth/services/gitlab.py
index 62313c89d..aef754c46 100644
--- a/readthedocs/oauth/services/gitlab.py
+++ b/readthedocs/oauth/services/gitlab.py
@@ -331,6 +331,12 @@ class GitLabService(Service):
                 log.info(
                     'GitLab webhook update successful for project: %s', project)
                 return (True, resp)
+
+            # GitLab returns 404 when the webhook doesn't exist. In this case,
+            # we call ``setup_webhook`` to re-configure it from scratch
+            if resp.status_code == 404:
+                return self.setup_webhook(project)
+
         # Catch exceptions with request or deserializing JSON
         except (RequestException, ValueError):
             log.exception(