diff --git a/roles/trigger-readthedocs/README.rst b/roles/trigger-readthedocs/README.rst
new file mode 100644
index 000000000..6a1e6af78
--- /dev/null
+++ b/roles/trigger-readthedocs/README.rst
@@ -0,0 +1,35 @@
+Trigger readthedocs build for a project
+
+**Role Variables**
+
+.. zuul:rolevar:: rtd_project_name
+   :default: ``{{ zuul.project.short_name }}``
+
+   The readthedocs project name
+
+.. zuul:rolevar:: rtd_webhook_id
+
+   The readthedocs webhook API ID.  This needs to be taken from the
+   project's "Integrations" dashboard page in RTD.  The URL will look
+   like ``readthedocs.org/api/v2/webhook/<project-name>/<id>/``.
+
+   This may come from a secret, however it can not be triggered
+   without authentication.
+
+.. zuul:rolevar:: rtd_integration_token
+
+   The webhook integration token.  You'll find this value on the
+   project's "Integrations" dashboard page in RTD.  This is expected
+   to come from a secret.  This can be used instead of
+   username/password combo.
+
+.. zuul:rolevar:: rtd_username
+
+   The readthedocs username.  If set, this will be used to
+   authenticate in preference to any token set via
+   ``rtd_integration_token``.
+
+.. zuul:rolevar:: rtd_password
+
+   Password for ``rtd_username``.  Must be set if password is set.
+   This is expected to come from a secret.
diff --git a/roles/trigger-readthedocs/defaults/main.yaml b/roles/trigger-readthedocs/defaults/main.yaml
new file mode 100644
index 000000000..f89ff9d4f
--- /dev/null
+++ b/roles/trigger-readthedocs/defaults/main.yaml
@@ -0,0 +1,2 @@
+---
+rtd_project_name: "{{ zuul.project.short_name }}"
diff --git a/roles/trigger-readthedocs/tasks/main.yaml b/roles/trigger-readthedocs/tasks/main.yaml
new file mode 100644
index 000000000..181220056
--- /dev/null
+++ b/roles/trigger-readthedocs/tasks/main.yaml
@@ -0,0 +1,38 @@
+- name: Check for webhook id
+  fail:
+    msg: You must define the webhook  id.  Get this from the webhook info page on RTD
+  when: rtd_webhook_id is not defined
+
+- name: Check for an authentication type
+  fail:
+    msg: Must set either rtd_username or rtd_integration_token
+  when: (rtd_username is not defined) and (rtd_integration_token is not defined)
+
+- when: rtd_username is defined
+  block:
+    - name: Require password
+      fail:
+        msg: rtd_password is required when using rtd_username
+      when: rtd_password is not defined
+
+    - name: Trigger readthedocs build webhook via authentication
+      uri:
+        method: POST
+        url: 'https://readthedocs.org/api/v2/webhook/{{ rtd_project_name }}/{{ rtd_webhook_id }}/'
+        user: '{{ rtd_username }}'
+        password: '{{ rtd_password }}'
+        # NOTE(ianw): testing it seems the API doesn't respond with
+        # 401 so this is required
+        force_basic_auth: yes
+
+- when: rtd_integration_token is defined and
+        rtd_username is not defined
+  block:
+    - name: Trigger readthedocs build webhook via token
+      uri:
+        method: POST
+        url: 'https://readthedocs.org/api/v2/webhook/{{ rtd_project_name }}/{{ rtd_webhook_id }}/'
+        body_format: form-urlencoded
+        body:
+          token: '{{ rtd_integration_token }}'
+          follow_redirects: all