diff --git a/app/Actions/Webhook.php b/app/Actions/Webhook.php
index 2d11584e6..55bc855ce 100644
--- a/app/Actions/Webhook.php
+++ b/app/Actions/Webhook.php
@@ -3,18 +3,67 @@
 namespace BookStack\Actions;
 
 use BookStack\Interfaces\Loggable;
+use Illuminate\Database\Eloquent\Collection;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\HasMany;
 
 /**
  * @property int $id
  * @property string $name
  * @property string $endpoint
+ * @property Collection $trackedEvents
  */
 class Webhook extends Model implements Loggable
 {
+    protected $fillable = ['name', 'endpoint'];
+
     use HasFactory;
 
+    /**
+     * Define the tracked event relation a webhook.
+     */
+    public function trackedEvents(): HasMany
+    {
+        return $this->hasMany(WebhookTrackedEvent::class);
+    }
+
+    /**
+     * Update the tracked events for a webhook from the given list of event types.
+     */
+    public function updateTrackedEvents(array $events): void
+    {
+        $this->trackedEvents()->delete();
+
+        $eventsToStore = array_intersect($events, array_values(ActivityType::all()));
+        if (in_array('all', $events)) {
+            $eventsToStore = ['all'];
+        }
+
+        $trackedEvents = [];
+        foreach ($eventsToStore as $event) {
+            $trackedEvents[] = new WebhookTrackedEvent(['event' => $event]);
+        }
+
+        $this->trackedEvents()->saveMany($trackedEvents);
+    }
+
+    /**
+     * Check if this webhook tracks the given event.
+     */
+    public function tracksEvent(string $event): bool
+    {
+        return $this->trackedEvents->pluck('event')->contains($event);
+    }
+
+    /**
+     * Get a URL for this webhook within the settings interface.
+     */
+    public function getUrl(string $path = ''): string
+    {
+        return url('/settings/webhooks/' . $this->id . '/' . ltrim($path, '/'));
+    }
+
     /**
      * Get the string descriptor for this item.
      */
diff --git a/app/Actions/WebhookTrackedEvent.php b/app/Actions/WebhookTrackedEvent.php
new file mode 100644
index 000000000..a0530620a
--- /dev/null
+++ b/app/Actions/WebhookTrackedEvent.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace BookStack\Actions;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+/**
+ * @property int $id
+ * @property int $webhook_id
+ * @property string $event
+ */
+class WebhookTrackedEvent extends Model
+{
+    protected $fillable = ['event'];
+
+    use HasFactory;
+}
diff --git a/app/Http/Controllers/WebhookController.php b/app/Http/Controllers/WebhookController.php
index 15a31f312..497d623b2 100644
--- a/app/Http/Controllers/WebhookController.php
+++ b/app/Http/Controllers/WebhookController.php
@@ -20,8 +20,11 @@ class WebhookController extends Controller
      */
     public function index()
     {
-        // TODO - Get and pass webhooks
-        return view('settings.webhooks.index');
+        $webhooks = Webhook::query()
+            ->orderBy('name', 'desc')
+            ->with('trackedEvents')
+            ->get();
+        return view('settings.webhooks.index', ['webhooks' => $webhooks]);
     }
 
     /**
@@ -37,7 +40,16 @@ class WebhookController extends Controller
      */
     public function store(Request $request)
     {
-        // TODO - Create webhook
+        $validated = $this->validate($request, [
+            'name' => ['required', 'max:150'],
+            'endpoint' => ['required', 'url', 'max:500'],
+            'events' => ['required', 'array']
+        ]);
+
+        $webhook = new Webhook($validated);
+        $webhook->save();
+        $webhook->updateTrackedEvents(array_values($validated['events']));
+
         $this->logActivity(ActivityType::WEBHOOK_CREATE, $webhook);
         return redirect('/settings/webhooks');
     }
@@ -48,7 +60,9 @@ class WebhookController extends Controller
     public function edit(string $id)
     {
         /** @var Webhook $webhook */
-        $webhook = Webhook::query()->findOrFail($id);
+        $webhook = Webhook::query()
+            ->with('trackedEvents')
+            ->findOrFail($id);
 
         return view('settings.webhooks.edit', ['webhook' => $webhook]);
     }
@@ -58,10 +72,17 @@ class WebhookController extends Controller
      */
     public function update(Request $request, string $id)
     {
+        $validated = $this->validate($request, [
+            'name' => ['required', 'max:150'],
+            'endpoint' => ['required', 'url', 'max:500'],
+            'events' => ['required', 'array']
+        ]);
+
         /** @var Webhook $webhook */
         $webhook = Webhook::query()->findOrFail($id);
 
-        // TODO - Update
+        $webhook->fill($validated)->save();
+        $webhook->updateTrackedEvents($validated['events']);
 
         $this->logActivity(ActivityType::WEBHOOK_UPDATE, $webhook);
         return redirect('/settings/webhooks');
@@ -85,7 +106,7 @@ class WebhookController extends Controller
         /** @var Webhook $webhook */
         $webhook = Webhook::query()->findOrFail($id);
 
-        // TODO - Delete event type relations
+        $webhook->trackedEvents()->delete();
         $webhook->delete();
 
         $this->logActivity(ActivityType::WEBHOOK_DELETE, $webhook);
diff --git a/database/migrations/2021_12_07_111343_create_webhooks_table.php b/database/migrations/2021_12_07_111343_create_webhooks_table.php
index 7ccfe693d..2ded0b949 100644
--- a/database/migrations/2021_12_07_111343_create_webhooks_table.php
+++ b/database/migrations/2021_12_07_111343_create_webhooks_table.php
@@ -18,6 +18,18 @@ class CreateWebhooksTable extends Migration
             $table->string('name', 150);
             $table->string('endpoint', 500);
             $table->timestamps();
+
+            $table->index('name');
+        });
+
+        Schema::create('webhook_tracked_events', function (Blueprint $table) {
+            $table->increments('id');
+            $table->integer('webhook_id');
+            $table->string('event', 50);
+            $table->timestamps();
+
+            $table->index('event');
+            $table->index('webhook_id');
         });
     }
 
diff --git a/resources/js/components/webhook-events.js b/resources/js/components/webhook-events.js
index 54080d36e..aa50aa9d8 100644
--- a/resources/js/components/webhook-events.js
+++ b/resources/js/components/webhook-events.js
@@ -8,7 +8,7 @@ class WebhookEvents {
 
     setup() {
         this.checkboxes = this.$el.querySelectorAll('input[type="checkbox"]');
-        this.allCheckbox = this.$refs.all;
+        this.allCheckbox = this.$el.querySelector('input[type="checkbox"][value="all"]');
 
         this.$el.addEventListener('change', event => {
             if (event.target.checked && event.target === this.allCheckbox) {
diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php
index 6812075b3..209702d0e 100755
--- a/resources/lang/en/settings.php
+++ b/resources/lang/en/settings.php
@@ -246,6 +246,7 @@ return [
     'webhooks_events_all' => 'All system events',
     'webhooks_name' => 'Webhook Name',
     'webhooks_endpoint' => 'Webhook Endpoint',
+    'webhook_events_table_header' => 'Events',
     'webhooks_delete' => 'Delete Webhook',
     'webhooks_delete_warning' => 'This will fully delete this webhook, with the name \':webhookName\', from the system.',
     'webhooks_delete_confirm' => 'Are you sure you want to delete this webhook?',
diff --git a/resources/views/form/errors.blade.php b/resources/views/form/errors.blade.php
new file mode 100644
index 000000000..03cd4be88
--- /dev/null
+++ b/resources/views/form/errors.blade.php
@@ -0,0 +1,3 @@
+@if($errors->has($name))
+    <div class="text-neg text-small">{{ $errors->first($name) }}</div>
+@endif
\ No newline at end of file
diff --git a/resources/views/settings/webhooks/create.blade.php b/resources/views/settings/webhooks/create.blade.php
index b49afe415..d5fd1d38d 100644
--- a/resources/views/settings/webhooks/create.blade.php
+++ b/resources/views/settings/webhooks/create.blade.php
@@ -8,7 +8,7 @@
             @include('settings.parts.navbar', ['selected' => 'webhooks'])
         </div>
 
-        <form action="{{ url("/settings/webhooks/new") }}" method="POST">
+        <form action="{{ url("/settings/webhooks/create") }}" method="POST">
             @include('settings.webhooks.parts.form', ['title' => trans('settings.webhooks_create')])
         </form>
     </div>
diff --git a/resources/views/settings/webhooks/delete.blade.php b/resources/views/settings/webhooks/delete.blade.php
index a89b01171..65560f65f 100644
--- a/resources/views/settings/webhooks/delete.blade.php
+++ b/resources/views/settings/webhooks/delete.blade.php
@@ -13,7 +13,7 @@
             <p>{{ trans('settings.webhooks_delete_warning', ['webhookName' => $webhook->name]) }}</p>
 
 
-            <form action="{{ url("/settings/webhooks/{$role->id}") }}" method="POST">
+            <form action="{{ $webhook->getUrl() }}" method="POST">
                 {!! csrf_field() !!}
                 {!! method_field('DELETE') !!}
 
@@ -25,7 +25,7 @@
                     </div>
                     <div>
                         <div class="form-group text-right">
-                            <a href="{{ url("/settings/webhooks/{$role->id}") }}" class="button outline">{{ trans('common.cancel') }}</a>
+                            <a href="{{ $webhook->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
                             <button type="submit" class="button">{{ trans('common.confirm') }}</button>
                         </div>
                     </div>
diff --git a/resources/views/settings/webhooks/edit.blade.php b/resources/views/settings/webhooks/edit.blade.php
index d4e60cc14..a221b4ce7 100644
--- a/resources/views/settings/webhooks/edit.blade.php
+++ b/resources/views/settings/webhooks/edit.blade.php
@@ -7,7 +7,7 @@
             @include('settings.parts.navbar', ['selected' => 'webhooks'])
         </div>
 
-        <form action="{{ url("/settings/webhooks/{$webhook->id}") }}" method="POST">
+        <form action="{{ $webhook->getUrl() }}" method="POST">
             {!! method_field('PUT') !!}
             @include('settings.webhooks.parts.form', ['model' => $webhook, 'title' => trans('settings.webhooks_edit')])
         </form>
diff --git a/resources/views/settings/webhooks/index.blade.php b/resources/views/settings/webhooks/index.blade.php
index 8adf60835..999a458ec 100644
--- a/resources/views/settings/webhooks/index.blade.php
+++ b/resources/views/settings/webhooks/index.blade.php
@@ -14,10 +14,40 @@
                 <h1 class="list-heading">{{ trans('settings.webhooks') }}</h1>
 
                 <div class="text-right">
-                    <a href="{{ url("/settings/webhooks/create") }}" class="button outline">{{ trans('settings.webhooks_create') }}</a>
+                    <a href="{{ url("/settings/webhooks/create") }}"
+                       class="button outline">{{ trans('settings.webhooks_create') }}</a>
                 </div>
             </div>
 
+            @if(count($webhooks) > 0)
+
+                <table class="table">
+                    <tr>
+                        <th>{{ trans('common.name') }}</th>
+                        <th>{{ trans('settings.webhook_events_table_header') }}</th>
+                    </tr>
+                    @foreach($webhooks as $webhook)
+                        <tr>
+                            <td>
+                                <a href="{{ $webhook->getUrl() }}">{{ $webhook->name }}</a> <br>
+                                <span class="small text-muted italic">{{ $webhook->endpoint }}</span>
+                            </td>
+                            <td>
+                                @if($webhook->tracksEvent('all'))
+                                    {{ trans('settings.webhooks_events_all') }}
+                                @else
+                                    {{ $webhook->trackedEvents->count() }}
+                                @endif
+                            </td>
+                        </tr>
+                    @endforeach
+                </table>
+            @else
+                <p class="text-muted empty-text">
+                    {{ trans('common.no_items') }}
+                </p>
+            @endif
+
 
         </div>
     </div>
diff --git a/resources/views/settings/webhooks/parts/form.blade.php b/resources/views/settings/webhooks/parts/form.blade.php
index 935b01992..e2b3fc34d 100644
--- a/resources/views/settings/webhooks/parts/form.blade.php
+++ b/resources/views/settings/webhooks/parts/form.blade.php
@@ -24,22 +24,32 @@
 
         <div component="webhook-events">
             <label class="setting-list-label">{{ trans('settings.webhooks_events') }}</label>
+            @include('form.errors', ['name' => 'events'])
+
             <p class="small">{{ trans('settings.webhooks_events_desc') }}</p>
             <p class="text-warn small">{{ trans('settings.webhooks_events_warning') }}</p>
 
-            <div>
-                <label><input type="checkbox"
-                              name="events[]"
-                              value="all"
-                              refs="webhook-events@all">
-                    {{ trans('settings.webhooks_events_all') }}</label>
+            <div class="toggle-switch-list">
+                @include('form.custom-checkbox', [
+                    'name' => 'events[]',
+                    'value' => 'all',
+                    'label' => trans('settings.webhooks_events_all'),
+                    'checked' => old('events') ? in_array('all', old('events')) : (isset($webhook) ? $webhook->tracksEvent('all') : false),
+                ])
             </div>
 
-            <hr class="my-m">
+            <hr class="my-s">
 
-            <div class="dual-column-content">
+            <div class="dual-column-content toggle-switch-list">
                 @foreach(\BookStack\Actions\ActivityType::all() as $activityType)
-                    <label><input type="checkbox" name="events[]" value="{{ $activityType }}">{{ $activityType }}</label>
+                    <div>
+                        @include('form.custom-checkbox', [
+                           'name' => 'events[]',
+                           'value' => $activityType,
+                           'label' => $activityType,
+                           'checked' => old('events') ? in_array($activityType, old('events')) : (isset($webhook) ? $webhook->tracksEvent($activityType) : false),
+                       ])
+                    </div>
                 @endforeach
             </div>
         </div>
@@ -49,7 +59,7 @@
     <div class="form-group text-right">
         <a href="{{ url("/settings/webhooks") }}" class="button outline">{{ trans('common.cancel') }}</a>
         @if ($webhook->id ?? false)
-            <a href="{{ url("/settings/roles/delete/{$webhook->id}") }}" class="button outline">{{ trans('settings.webhooks_delete') }}</a>
+            <a href="{{ $webhook->getUrl('/delete') }}" class="button outline">{{ trans('settings.webhooks_delete') }}</a>
         @endif
         <button type="submit" class="button">{{ trans('settings.webhooks_save') }}</button>
     </div>