diff --git a/web/pages/webhooks.tsx b/web/pages/webhooks.tsx
new file mode 100644
index 000000000..0cad870b5
--- /dev/null
+++ b/web/pages/webhooks.tsx
@@ -0,0 +1,181 @@
+import React, { useState, useEffect } from "react";
+import { Table, Tag, Space, Button, Modal, Checkbox, Input, Typography, Tooltip, Select } from 'antd';
+import { DeleteOutlined, EyeTwoTone, EyeInvisibleOutlined } from '@ant-design/icons';
+
+const { Title, Paragraph, Text } = Typography;
+const { Option } = Select;
+
+import format from 'date-fns/format'
+
+import {
+ fetchData,
+ DELETE_WEBHOOK,
+ CREATE_WEBHOOK,
+ WEBHOOKS,
+} from "../utils/apis";
+
+const availableEvents = {
+ 'CHAT': { name: 'Chat messages', description: 'When a user sends a chat message', color: 'purple' },
+ 'USER_JOINED': { name: 'User joined', description: 'When a user joins the chat', color: 'green'},
+ 'NAME_CHANGE': { name: 'User name changed', description: 'When a user changes their name', color: 'blue'},
+ 'VISIBILITY-UPDATE': { name: 'Message visibility changed', description: 'When a message visibility changes, likely due to moderation', color: 'red'},
+ 'STREAM_STARTED': {name: 'Stream started', description: 'When a stream starts', color: 'orange'},
+ 'STREAM_STOPPED': {name: 'Stream stopped', description: 'When a stream stops', color: 'cyan'}
+
+};
+
+function convertEventStringToTag(eventString) {
+ if (!eventString || !availableEvents[eventString]) {
+ return null;
+ }
+
+ const event = availableEvents[eventString];
+
+ return (
+
+
+ {event.name}
+
+
+ );
+}
+
+function NewWebhookModal(props) {
+ var selectedEvents = [];
+ const [name, setName] = useState('');
+
+ const events = Object.keys(availableEvents).map(function (key) {
+ return { value: key, label: availableEvents[key].description }
+ });
+
+
+ function onChange(checkedValues) {
+ selectedEvents = checkedValues
+ }
+
+ function save() {
+ props.onOk(name, selectedEvents)
+ }
+
+ return (
+
+ setName(input.currentTarget.value)} />
+
+
+ Select the events that will be sent to this webhook.
+
+
+
+ )
+}
+
+export default function Webhooks() {
+ const [webhooks, setWebhooks] = useState([]);
+ const [isModalVisible, setIsModalVisible] = useState(false);
+
+ const columns = [
+ {
+ title: '',
+ key: 'delete',
+ render: (text, record) => (
+
+ handleDelete(record.id)} icon={ } />
+
+ )
+ },
+ {
+ title: 'URL',
+ dataIndex: 'url',
+ key: 'url',
+ },
+ {
+ title: 'Events',
+ dataIndex: 'events',
+ key: 'events',
+ render: events => (
+ <>
+ {events.map(event => {
+ return convertEventStringToTag(event);
+ })}
+ >
+ ),
+ },
+ {
+ title: 'Last Used',
+ dataIndex: 'lastUsed',
+ key: 'lastUsed',
+ render: (lastUsed) => {
+ if (!lastUsed) {
+ return 'Never';
+ }
+ const dateObject = new Date(lastUsed);
+ return format(dateObject, 'P p');
+ },
+ },
+ ];
+
+ const getWebhooks = async () => {
+ try {
+ const result = await fetchData(WEBHOOKS);
+ setWebhooks(result);
+ } catch (error) {
+ handleError(error);
+ }
+ };
+
+ useEffect(() => {
+ getWebhooks();
+ }, []);
+
+ async function handleDelete(id) {
+ try {
+ const result = await fetchData(DELETE_WEBHOOK, { method: 'POST', data: { id: id } });
+ getWebhooks();
+ } catch (error) {
+ handleError(error);
+ }
+ }
+
+ async function handleSave(url: string, events: string[]) {
+ try {
+ const newHook = await fetchData(CREATE_WEBHOOK, { method: 'POST', data: { url: url, events: events } });
+ setWebhooks(webhooks.concat(newHook));
+ } catch (error) {
+ handleError(error);
+ }
+ }
+
+ function handleError(error) {
+ console.error("error", error);
+ alert(error);
+ }
+
+ const showCreateModal = () => {
+ setIsModalVisible(true);
+ };
+
+ const handleModalSaveButton = (url, events) => {
+ setIsModalVisible(false);
+ handleSave(url, events);
+ };
+
+ const handleModalCancelButton = () => {
+ setIsModalVisible(false);
+ };
+
+ return (
+
+
Webhooks
+
+ A webhook is a callback made to an external API in response to an event. These are endpoints that live outside of Owncast and run code who wants to be made aware of events that take place on your server.
+
+
+ Read more about how to use webhooks at _some documentation here_.
+
+
+
+
Create Webhook
+
+
+ );
+}
diff --git a/web/utils/apis.ts b/web/utils/apis.ts
index 0c60790c1..a859080c8 100644
--- a/web/utils/apis.ts
+++ b/web/utils/apis.ts
@@ -40,6 +40,14 @@ export const CHAT_HISTORY = `${API_LOCATION}chat/messages`;
// Get chat history
export const UPDATE_CHAT_MESSGAE_VIZ = `${NEXT_PUBLIC_API_HOST}api/admin/chat/updatemessagevisibility`;
+// Get webhooks
+export const WEBHOOKS = `${API_LOCATION}webhooks`;
+
+// Delete a single webhook
+export const DELETE_WEBHOOK = `${API_LOCATION}webhooks/delete`;
+
+// Create a single webhook
+export const CREATE_WEBHOOK = `${API_LOCATION}webhooks/create`;
const GITHUB_RELEASE_URL = "https://api.github.com/repos/owncast/owncast/releases/latest";