diff --git a/docs/api/endpoints/v0/users.mdx b/docs/api/endpoints/v0/users.mdx
index 3f0de53..bd47647 100644
--- a/docs/api/endpoints/v0/users.mdx
+++ b/docs/api/endpoints/v0/users.mdx
@@ -54,7 +54,7 @@ hide_table_of_contents: true
The email address of the user.
- The preferred name of the user. If provided, this should be used instead of the first and ast name, except where legally required.
+ The preferred name of the user. If provided, this should be used instead of the first and last name, except where legally required.
The first name of the user.
diff --git a/docs/api/webhooks/_category_.json b/docs/api/webhooks/_category_.json
new file mode 100644
index 0000000..4164b64
--- /dev/null
+++ b/docs/api/webhooks/_category_.json
@@ -0,0 +1,4 @@
+{
+ "label": "Webhooks",
+ "position": 3
+}
\ No newline at end of file
diff --git a/docs/api/webhooks/about.md b/docs/api/webhooks/about.md
new file mode 100644
index 0000000..b289209
--- /dev/null
+++ b/docs/api/webhooks/about.md
@@ -0,0 +1,9 @@
+# About Webhooks
+
+Webhooks allow you to set up external applications to receive notifications when certain events happen. When a webhook event is triggered, we'll send a HTTP POST payload to the provided URL for the application.
+
+These webhook events can be used to trigger automated actions in third-party applications, such as updating access control, mailing lists, and more.
+
+## Test event
+
+When you setup a webhook, you can send a test event to verify that the webhook is working correctly by using the **Test** button in Housekeeping.
\ No newline at end of file
diff --git a/docs/api/webhooks/events.md b/docs/api/webhooks/events.md
new file mode 100644
index 0000000..c9f9f80
--- /dev/null
+++ b/docs/api/webhooks/events.md
@@ -0,0 +1,846 @@
+# Events and Payloads
+
+You can create webhooks that subscribe to one or more events listed on this page. Each webhook event listed here includes a description of the properties and an example payload.
+
+## Common properties
+
+Each webhook, in addition to the event-specific properties, will include the following common properties:
+
+
+ The name of the event that triggered the webhook.
+
+
+ The event-specific data. See the corresponding section under event types for more information.
+
+
+## Common headers
+
+Each POST request will include the following headers:
+
+
+ The HTTP client used to send the request. This will always be ConCat/AxiosHTTPClient.
+
+
+ A bearer token with a signed JWT payload containing a base64-encoded sha256 hash of the data property. See Validating Payloads for more information.
+
+
+## Event types
+
+### `volunteer-submitted`
+
+
+
When a user creates a new volunteer application.
+
+ The volunteer record containing the user's application.
+ <>
+
+ The unique identifier of the volunteer application.
+
+
+ The preferred method of contact for the volunteer.
+
+
+ The contact methods for the volunteer.
+ <>
+
+ A string representing the type of contact method.
+
+
+ An arbitrary string provided by the user for the contact method.
+
+ >
+
+
+ Whether the volunteer is available before the event.
+
+
+ Any additional information the user provided.
+
+
+ The user's previous convention experience.
+
+
+ The user's previous experience outside of events that may relate to their preferred departments.
+
+
+ Any panels or events the user would not want to miss.
+
+
+ The date the volunteer application was created.
+
+
+ The date the volunteer application was last updated.
+
+ >
+
+
+ The user who created the volunteer application.
+ <>
+
+ The unique identifier of the user.
+
+
+ The username of the user.
+
+
+ The first name of the user.
+
+
+ The preferred name of the user. If provided, this should be used instead of the first and last name, except where legally required.
+
+
+ The last name of the user.
+
+
+ The email address of the user.
+
+
+ Whether the user's email address has been verified.
+
+
+ The date the user was created.
+
+
+ The date the user was last updated.
+
+ >
+
+
+
+
+ {`{
+ "event": "volunteer-submitted",
+ "data": {
+ "volunteer": {
+ "id": "1234",
+ "contactMethod": "email",
+ "contactMethods": {
+ "email": "johnny.test@concat.systems",
+ "discord": "JohnnyTest#1234",
+ },
+ "availableBeforeCon": true,
+ "anythingElse": "I'm a pretty cool guy.",
+ "previousConExperience": "I've been to a few conventions.",
+ "previousOtherExperience": "I've worked at a few conventions.",
+ "eventsCanNotMiss": "I really want to see the panel on how to make a better sandwich.",
+ "createdAt": "2020-01-01T00:00:00.000Z",
+ "updatedAt": "2020-01-01T00:00:00.000Z"
+ },
+ "user": {
+ "id": "1234",
+ "username": "JohnnyTest",
+ "firstName": "Johnny",
+ "preferredName": "Johnny",
+ "lastName": "Test",
+ "email": "johnny.test@concat.systems",
+ "verified": true,
+ "createdAt": "2020-01-01T00:00:00.000Z",
+ "updatedAt": "2020-01-01T00:00:00.000Z"
+ }
+ }
+}`}
+
+
+
+
+### `volunteer-updated`
+
+
+
When an existing volunteer application is updated.
+
+ The volunteer record containing the user's application.
+ <>
+
+ The unique identifier of the volunteer application.
+
+
+ The preferred method of contact for the volunteer.
+
+
+ The contact methods for the volunteer.
+ <>
+
+ A string representing the type of contact method.
+
+
+ An arbitrary string provided by the user for the contact method.
+
+ >
+
+
+ Whether the volunteer is available before the event.
+
+
+ Any additional information the user provided.
+
+
+ The user's previous convention experience.
+
+
+ The user's previous experience outside of events that may relate to their preferred departments.
+
+
+ Any panels or events the user would not want to miss.
+
+
+ The date the volunteer application was created.
+
+
+ The date the volunteer application was last updated.
+
+ >
+
+
+ The user who created the volunteer application.
+ <>
+
+ The unique identifier of the user.
+
+
+ The username of the user.
+
+
+ The first name of the user.
+
+
+ The preferred name of the user. If provided, this should be used instead of the first and last name, except where legally required.
+
+
+ The last name of the user.
+
+
+ The email address of the user.
+
+
+ Whether the user's email address has been verified.
+
+
+ The date the user was created.
+
+
+ The date the user was last updated.
+
+ >
+
+
+
+
+ {`{
+ "event": "volunteer-updated",
+ "data": {
+ "volunteer": {
+ "id": "1234",
+ "contactMethod": "email",
+ "contactMethods": {
+ "email": "johnny.test@concat.systems",
+ "discord": "JohnnyTest#1234",
+ },
+ "availableBeforeCon": true,
+ "anythingElse": "I'm a pretty cool guy.",
+ "previousConExperience": "I've been to a few conventions.",
+ "previousOtherExperience": "I've worked at a few conventions.",
+ "eventsCanNotMiss": "I really want to see the panel on how to make a better sandwich.",
+ "createdAt": "2020-01-01T00:00:00.000Z",
+ "updatedAt": "2020-01-01T00:00:00.000Z"
+ },
+ "user": {
+ "id": "1234",
+ "username": "JohnnyTest",
+ "firstName": "Johnny",
+ "preferredName": "Johnny",
+ "lastName": "Test",
+ "email": "johnny.test@concat.systems",
+ "verified": true,
+ "createdAt": "2020-01-01T00:00:00.000Z",
+ "updatedAt": "2020-01-01T00:00:00.000Z"
+ }
+ }
+}`}
+
+
+
+
+### `volunteer-assigned`
+
+
+
When a volunteer is assigned to a department.
+
+ The volunteer record containing the user's application.
+ <>
+
+ The unique identifier of the volunteer application.
+
+
+ The preferred method of contact for the volunteer.
+
+
+ The contact methods for the volunteer.
+ <>
+
+ A string representing the type of contact method.
+
+
+ An arbitrary string provided by the user for the contact method.
+
+ >
+
+
+ Whether the volunteer is available before the event.
+
+
+ Any additional information the user provided.
+
+
+ The user's previous convention experience.
+
+
+ The user's previous experience outside of events that may relate to their preferred departments.
+
+
+ Any panels or events the user would not want to miss.
+
+
+ The date the volunteer application was created.
+
+
+ The date the volunteer application was last updated.
+
+ >
+
+
+ The user who created the volunteer application.
+ <>
+
+ The unique identifier of the user.
+
+
+ The username of the user.
+
+
+ The first name of the user.
+
+
+ The preferred name of the user. If provided, this should be used instead of the first and last name, except where legally required.
+
+
+ The last name of the user.
+
+
+ The email address of the user.
+
+
+ Whether the user's email address has been verified.
+
+
+ The date the user was created.
+
+
+ The date the user was last updated.
+
+ >
+
+
+ The department the volunteer was assigned to.
+ <>
+
+ The unique identifier of the department.
+
+
+ The name of the department.
+
+
+ The email address for contacting the department lead.
+
+
+ Whether the department is publicly visible.
+
+
+ The date the department was created.
+
+
+ The date the department was last updated.
+
+ >
+
+
+
+
+ {`{
+ "event": "volunteer-assigned",
+ "data": {
+ "volunteer": {
+ "id": "1234",
+ "contactMethod": "email",
+ "contactMethods": {
+ "email": "johnny.test@concat.systems",
+ "discord": "JohnnyTest#1234",
+ },
+ "availableBeforeCon": true,
+ "anythingElse": "I'm a pretty cool guy.",
+ "previousConExperience": "I've been to a few conventions.",
+ "previousOtherExperience": "I've worked at a few conventions.",
+ "eventsCanNotMiss": "I really want to see the panel on how to make a better sandwich.",
+ "createdAt": "2020-01-01T00:00:00.000Z",
+ "updatedAt": "2020-01-01T00:00:00.000Z"
+ },
+ "user": {
+ "id": "1234",
+ "username": "JohnnyTest",
+ "firstName": "Johnny",
+ "preferredName": "Johnny",
+ "lastName": "Test",
+ "email": "johnny.test@concat.systems",
+ "verified": true,
+ "createdAt": "2020-01-01T00:00:00.000Z",
+ "updatedAt": "2020-01-01T00:00:00.000Z"
+ },
+ "department": {
+ "id": "1234",
+ "name": "Registration",
+ "email": "registration@concat.event",
+ "publiclyVisible": true,
+ "createdAt": "2020-01-01T00:00:00.000Z",
+ "updatedAt": "2020-01-01T00:00:00.000Z"
+ }
+ }
+}`}
+
+
+
+
+### `volunteer-unassigned`
+
+
+
When a volunteer is removed from a department.
+
+ The volunteer record containing the user's application.
+ <>
+
+ The unique identifier of the volunteer application.
+
+
+ The preferred method of contact for the volunteer.
+
+
+ The contact methods for the volunteer.
+ <>
+
+ A string representing the type of contact method.
+
+
+ An arbitrary string provided by the user for the contact method.
+
+ >
+
+
+ Whether the volunteer is available before the event.
+
+
+ Any additional information the user provided.
+
+
+ The user's previous convention experience.
+
+
+ The user's previous experience outside of events that may relate to their preferred departments.
+
+
+ Any panels or events the user would not want to miss.
+
+
+ The date the volunteer application was created.
+
+
+ The date the volunteer application was last updated.
+
+ >
+
+
+ The user who created the volunteer application.
+ <>
+
+ The unique identifier of the user.
+
+
+ The username of the user.
+
+
+ The first name of the user.
+
+
+ The preferred name of the user. If provided, this should be used instead of the first and last name, except where legally required.
+
+
+ The last name of the user.
+
+
+ The email address of the user.
+
+
+ Whether the user's email address has been verified.
+
+
+ The date the user was created.
+
+
+ The date the user was last updated.
+
+ >
+
+
+ The department the volunteer was removed from.
+ <>
+
+ The unique identifier of the department.
+
+
+ The name of the department.
+
+
+ The email address for contacting the department lead.
+
+
+ Whether the department is publicly visible.
+
+
+ The date the department was created.
+
+
+ The date the department was last updated.
+
+ >
+
+
+
+
+ {`{
+ "event": "volunteer-unassigned",
+ "data": {
+ "volunteer": {
+ "id": "1234",
+ "contactMethod": "email",
+ "contactMethods": {
+ "email": "johnny.test@concat.systems",
+ "discord": "JohnnyTest#1234",
+ },
+ "availableBeforeCon": true,
+ "anythingElse": "I'm a pretty cool guy.",
+ "previousConExperience": "I've been to a few conventions.",
+ "previousOtherExperience": "I've worked at a few conventions.",
+ "eventsCanNotMiss": "I really want to see the panel on how to make a better sandwich.",
+ "createdAt": "2020-01-01T00:00:00.000Z",
+ "updatedAt": "2020-01-01T00:00:00.000Z"
+ },
+ "user": {
+ "id": "1234",
+ "username": "JohnnyTest",
+ "firstName": "Johnny",
+ "preferredName": "Johnny",
+ "lastName": "Test",
+ "email": "johnny.test@concat.systems",
+ "verified": true,
+ "createdAt": "2020-01-01T00:00:00.000Z",
+ "updatedAt": "2020-01-01T00:00:00.000Z"
+ },
+ "department": {
+ "id": "1234",
+ "name": "Registration",
+ "email": "registration@concat.event",
+ "publiclyVisible": true,
+ "createdAt": "2020-01-01T00:00:00.000Z",
+ "updatedAt": "2020-01-01T00:00:00.000Z"
+ }
+ }
+}`}
+
+
+
+
+### `volunteer-deleted`
+
+
+
When a volunteer has their volunteer application deleted.
+
+ The volunteer record containing the user's application.
+ <>
+
+ The unique identifier of the volunteer application.
+
+
+ The preferred method of contact for the volunteer.
+
+
+ The contact methods for the volunteer.
+ <>
+
+ A string representing the type of contact method.
+
+
+ An arbitrary string provided by the user for the contact method.
+
+ >
+
+
+ Whether the volunteer is available before the event.
+
+
+ Any additional information the user provided.
+
+
+ The user's previous convention experience.
+
+
+ The user's previous experience outside of events that may relate to their preferred departments.
+
+
+ Any panels or events the user would not want to miss.
+
+
+ The date the volunteer application was created.
+
+
+ The date the volunteer application was last updated.
+
+ >
+
+
+ The user who created the volunteer application.
+ <>
+
+ The unique identifier of the user.
+
+
+ The username of the user.
+
+
+ The first name of the user.
+
+
+ The preferred name of the user. If provided, this should be used instead of the first and last name, except where legally required.
+
+
+ The last name of the user.
+
+
+ The email address of the user.
+
+
+ Whether the user's email address has been verified.
+
+
+ The date the user was created.
+
+
+ The date the user was last updated.
+
+ >
+
+
+ The department the volunteer was assigned to.
+ <>
+
+ The unique identifier of the department.
+
+
+ The name of the department.
+
+
+ The email address for contacting the department lead.
+
+
+ Whether the department is publicly visible.
+
+
+ The date the department was created.
+
+
+ The date the department was last updated.
+
+ >
+
+
The user created or updated their attendee registration.
+
+ The registration record containing the user's event registration.
+ <>
+
+ The unique identifier of the attendance type product.
+
+
+ The unique identifier for the user's chosen badge art.
+
+
+ The user's chosen badge name.
+
+
+ The unique identifier of the registration.
+
+
+ The unique identifier of the user.
+
+
+ The date the registration was created.
+
+
+ The date the registration was last updated.
+
+ >
+
+
+ The user who created the registration.
+ <>
+
+ The unique identifier of the user.
+
+
+ The username of the user.
+
+
+ The first name of the user.
+
+
+ The preferred name of the user. If provided, this should be used instead of the first and last name, except where legally required.
+
+
+ The last name of the user.
+
+
+ The email address of the user.
+
+
+ Whether the user's email address has been verified.
+
+
+ The date the user was created.
+
+
+ The date the user was last updated.
+
+
+ The social accounts the user has linked to their user profile.
+ <>
+
+ The name of the social account (e.g. "twitter" or "discord").
+
+
+ The unique identifier of the social account (e.g. "1234" or "JohnnyTest#1234"), with the format depending on the third-party service.
+
+ >
+
+ >
+
+
+
+ The user's profile.
+ <>
+
+ The unique identifier of the user.
+
+
+ The username of the user.
+
+
+ The first name of the user.
+
+
+ The preferred name of the user. If provided, this should be used instead of the first and last name, except where legally required.
+
+
+ The last name of the user.
+
+
+ The email address of the user.
+
+
+ Whether the user's email address has been verified.
+
+
+ The date the user was created.
+
+
+ The date the user was last updated.
+
+
+ A unique identifier for the organization the user account belongs to. This is used to share a single account across all events run by the same organization.
+
+ >
+
+
+
\ No newline at end of file
diff --git a/docs/api/webhooks/validating.md b/docs/api/webhooks/validating.md
new file mode 100644
index 0000000..7be924f
--- /dev/null
+++ b/docs/api/webhooks/validating.md
@@ -0,0 +1,35 @@
+# Validating Payloads
+
+Each webhook POST request will include a signed JWT payload in the `authorization` header. The payload contains a Base64 **sha256** hash of the `data` property. You can use this hash to verify that the payload was sent by ConCat.
+
+## JWT format
+
+The JWT payload will contain the following properties:
+
+| Key | Description |
+| --- | ----------- |
+| `int` | The SHA256 hash of the `data` property. |
+| `exp` | The expiration time of the JWT payload. JWT events are valid for 120 seconds from creation. |
+| `iat` | The time the JWT payload was created. |
+| `aud` | The URL of the webhook. |
+| `iss` | Always `concat.app`. |
+| `sub` | Always `webhook`. |
+
+## Verifying the JWT
+
+We **strongly** recommend verifying the JWT payload before processing the webhook event. At a minimum, you should verify the following:
+
+ * The `aud` property matches the URL of the webhook.
+ * The `exp` property is greater than the current time.
+ * The `iss` property is `concat.app`.
+ * The `sub` property is `webhook`.
+
+### Verifiying the JWT signature
+
+The JWT payload will be signed by ConCat and can be validated with the public key found at the `/api/webhooks/key` endpoint of your ConCat instance. The public key is a PEM-encoded X.509 RSA public key.
+
+We recommend using one of the supported JWT libraries listed on the [jwt.io](https://jwt.io/libraries) website.
+
+## Verifying the payload hash
+
+The `int` property of the JWT payload is a Base64-encoded SHA256 hash of the `data` property of the webhook payload. You can use this hash to verify that the payload was not modified by a third-party or damaged in transit.
\ No newline at end of file
diff --git a/src/components/attribute.module.scss b/src/components/attribute.module.scss
index 691c6e1..98ecec2 100644
--- a/src/components/attribute.module.scss
+++ b/src/components/attribute.module.scss
@@ -1,7 +1,7 @@
.attribute, .attribute-parent {
margin-top: 0.5rem;
padding-bottom: 0.5rem;
- margin-left: 1.3rem;
+ margin-left: 1rem;
}
.attribute {