diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1478,13 +1478,13 @@
     'HarbormasterBuildableTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php',
     'HarbormasterBuildableTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildableTransactionQuery.php',
     'HarbormasterBuildableViewController' => 'applications/harbormaster/controller/HarbormasterBuildableViewController.php',
-    'HarbormasterBuildkiteBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php',
+    'HarbormasterBuildkiteBuildStepImplementation' => 'applications/harbormaster/integration/buildkite/HarbormasterBuildkiteBuildStepImplementation.php',
     'HarbormasterBuildkiteBuildableInterface' => 'applications/harbormaster/interface/HarbormasterBuildkiteBuildableInterface.php',
-    'HarbormasterBuildkiteHookController' => 'applications/harbormaster/controller/HarbormasterBuildkiteHookController.php',
+    'HarbormasterBuildkiteHookHandler' => 'applications/harbormaster/integration/buildkite/HarbormasterBuildkiteHookHandler.php',
     'HarbormasterBuiltinBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php',
-    'HarbormasterCircleCIBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php',
+    'HarbormasterCircleCIBuildStepImplementation' => 'applications/harbormaster/integration/circleci/HarbormasterCircleCIBuildStepImplementation.php',
     'HarbormasterCircleCIBuildableInterface' => 'applications/harbormaster/interface/HarbormasterCircleCIBuildableInterface.php',
-    'HarbormasterCircleCIHookController' => 'applications/harbormaster/controller/HarbormasterCircleCIHookController.php',
+    'HarbormasterCircleCIHookHandler' => 'applications/harbormaster/integration/circleci/HarbormasterCircleCIHookHandler.php',
     'HarbormasterConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php',
     'HarbormasterControlBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterControlBuildStepGroup.php',
     'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php',
@@ -1498,6 +1498,8 @@
     'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php',
     'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php',
     'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php',
+    'HarbormasterHookController' => 'applications/harbormaster/controller/HarbormasterHookController.php',
+    'HarbormasterHookHandler' => 'applications/harbormaster/integration/HarbormasterHookHandler.php',
     'HarbormasterHostArtifact' => 'applications/harbormaster/artifact/HarbormasterHostArtifact.php',
     'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php',
     'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php',
@@ -7727,10 +7729,10 @@
     'HarbormasterBuildableTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'HarbormasterBuildableViewController' => 'HarbormasterController',
     'HarbormasterBuildkiteBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
-    'HarbormasterBuildkiteHookController' => 'HarbormasterController',
+    'HarbormasterBuildkiteHookHandler' => 'HarbormasterHookHandler',
     'HarbormasterBuiltinBuildStepGroup' => 'HarbormasterBuildStepGroup',
     'HarbormasterCircleCIBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
-    'HarbormasterCircleCIHookController' => 'HarbormasterController',
+    'HarbormasterCircleCIHookHandler' => 'HarbormasterHookHandler',
     'HarbormasterConduitAPIMethod' => 'ConduitAPIMethod',
     'HarbormasterControlBuildStepGroup' => 'HarbormasterBuildStepGroup',
     'HarbormasterController' => 'PhabricatorController',
@@ -7744,6 +7746,8 @@
     'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup',
     'HarbormasterFileArtifact' => 'HarbormasterArtifact',
     'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
+    'HarbormasterHookController' => 'HarbormasterController',
+    'HarbormasterHookHandler' => 'Phobject',
     'HarbormasterHostArtifact' => 'HarbormasterDrydockLeaseArtifact',
     'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
     'HarbormasterLintMessagesController' => 'HarbormasterController',
diff --git a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php
--- a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php
+++ b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php
@@ -94,10 +94,7 @@
         'lint/' => array(
           '(?P<id>\d+)/' => 'HarbormasterLintMessagesController',
         ),
-        'hook/' => array(
-          'circleci/' => 'HarbormasterCircleCIHookController',
-          'buildkite/' => 'HarbormasterBuildkiteHookController',
-        ),
+        'hook/(?P<handler>[^/]+)/' => 'HarbormasterHookController',
         'log/' => array(
           'view/(?P<id>\d+)/(?:\$(?P<lines>\d+(?:-\d+)?))?'
             => 'HarbormasterBuildLogViewController',
diff --git a/src/applications/harbormaster/controller/HarbormasterHookController.php b/src/applications/harbormaster/controller/HarbormasterHookController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/controller/HarbormasterHookController.php
@@ -0,0 +1,21 @@
+<?php
+
+final class HarbormasterHookController
+  extends HarbormasterController {
+
+  public function shouldRequireLogin() {
+    return false;
+  }
+
+  public function handleRequest(AphrontRequest $request) {
+    $name = $request->getURIData('handler');
+    $handler = HarbormasterHookHandler::getHandler($name);
+
+    if (!$handler) {
+      throw new Exception(pht('No handler found for %s', $name));
+    }
+
+    return $handler->handleRequest($request);
+  }
+
+}
diff --git a/src/applications/harbormaster/integration/HarbormasterHookHandler.php b/src/applications/harbormaster/integration/HarbormasterHookHandler.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/integration/HarbormasterHookHandler.php
@@ -0,0 +1,27 @@
+<?php
+
+abstract class HarbormasterHookHandler
+  extends Phobject {
+
+  public static function getHandlers() {
+    return id(new PhutilClassMapQuery())
+      ->setAncestorClass(__CLASS__)
+      ->setUniqueMethod('getName')
+      ->execute();
+  }
+
+  public static function getHandler($handler) {
+    $base = idx(self::getHandlers(), $handler);
+
+    if ($base) {
+      return (clone $base);
+    }
+
+    return null;
+  }
+
+  abstract public function getName();
+
+  abstract public function handleRequest(AphrontRequest $request);
+
+}
diff --git a/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php b/src/applications/harbormaster/integration/buildkite/HarbormasterBuildkiteBuildStepImplementation.php
rename from src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php
rename to src/applications/harbormaster/integration/buildkite/HarbormasterBuildkiteBuildStepImplementation.php
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildkiteHookController.php b/src/applications/harbormaster/integration/buildkite/HarbormasterBuildkiteHookHandler.php
rename from src/applications/harbormaster/controller/HarbormasterBuildkiteHookController.php
rename to src/applications/harbormaster/integration/buildkite/HarbormasterBuildkiteHookHandler.php
--- a/src/applications/harbormaster/controller/HarbormasterBuildkiteHookController.php
+++ b/src/applications/harbormaster/integration/buildkite/HarbormasterBuildkiteHookHandler.php
@@ -1,10 +1,10 @@
 <?php
 
-final class HarbormasterBuildkiteHookController
-  extends HarbormasterController {
+final class HarbormasterBuildkiteHookHandler
+  extends HarbormasterHookHandler {
 
-  public function shouldRequireLogin() {
-    return false;
+  public function getName() {
+    return 'buildkite';
   }
 
   /**
diff --git a/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php b/src/applications/harbormaster/integration/circleci/HarbormasterCircleCIBuildStepImplementation.php
rename from src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php
rename to src/applications/harbormaster/integration/circleci/HarbormasterCircleCIBuildStepImplementation.php
diff --git a/src/applications/harbormaster/controller/HarbormasterCircleCIHookController.php b/src/applications/harbormaster/integration/circleci/HarbormasterCircleCIHookHandler.php
rename from src/applications/harbormaster/controller/HarbormasterCircleCIHookController.php
rename to src/applications/harbormaster/integration/circleci/HarbormasterCircleCIHookHandler.php
--- a/src/applications/harbormaster/controller/HarbormasterCircleCIHookController.php
+++ b/src/applications/harbormaster/integration/circleci/HarbormasterCircleCIHookHandler.php
@@ -1,10 +1,10 @@
 <?php
 
-final class HarbormasterCircleCIHookController
-  extends HarbormasterController {
+final class HarbormasterCircleCIHookHandler
+  extends HarbormasterHookHandler {
 
-  public function shouldRequireLogin() {
-    return false;
+  public function getName() {
+    return 'circleci';
   }
 
   /**