# Guide to Document360 Webhooks: Features and Best Practices

Document360 webhooks send knowledge base activity to your own systems. When an article is published, a category is deleted or a workflow status changes, a webhook notification channel can send the event to your service, so you can purge a content cache, start a review workflow in another tool, or keep an audit trail outside Document360.

This guide covers how webhook channels and notification mapping fit together, how to authenticate requests with a secret header, what the payloads look like, and the practices that make a Document360 receiver dependable.

## What are Document360 webhooks?

Document360 sends notifications through **channels**, such as email, Slack, Microsoft Teams and **Webhook**. Setting one up takes two steps:

1. Create a **Webhook notification channel** with your URL, method and headers.
2. Under **Notification mapping**, turn on the events you want and map them to the channel.

A channel with no mapped events sends nothing. All events are off by default, and Document360 notes that "enabling a toggle without a mapped channel will not deliver notifications".

Document360 doesn't sign webhook requests. To tell a real request from a forged one, add a secret header to the channel and check it on your side.

## Document360 webhook features

| Feature | Details |
| --- | --- |
| Configuration | **Settings** > **Knowledge base portal** > **Notifications** > **Channels** > **New channel** > **Webhook** |
| Event mapping | **Notifications** > **Notification mapping**, Owner or Admin role |
| Method | `POST` or `PUT`, chosen per channel |
| Format | JSON, with display-label keys such as `"Article name"` |
| Custom headers | Key-value pairs |
| Authorization key field | Header name not documented |
| Events | About 200, grouped into modules |
| Signature and timestamp | None |
| History | A **Notification history** tab |
| Retries, timeout, source IPs | Not documented |

## Events and payloads

The **Notification mapping** tab groups events into modules: Documentation editor, Drive, Knowledge base portal settings, Knowledge base site settings, Knowledge base widget settings, Users & security settings, Tools settings, Knowledge base site, AI notifications, Analytics, and All comments notification. You can turn on everything, a whole module, or individual events, then map them to one or more channels.

Some commonly used events:

| Module | Events |
| --- | --- |
| Documentation editor | Article created, Article published, Article deleted, Article settings updated, Article slug updated, Article workflow status updated, Category created, Category published, Category deleted |
| Drive | Folder added, Folder renamed, Folder deleted, File(s) added, File(s) updated, File(s) deleted |
| Knowledge base portal settings | Workspace created, Workspace updated, Workspace deleted, Notification channel deleted |

The payload is JSON, and its keys are display labels with spaces. This is Document360's example for a deleted article:

```json
{
  "Knowledge base name": "My Knowledge Base",
  "Event": "Article deleted",
  "User": "John Doe",
  "User email": "johndoe@example.com",
  "Logged on (UTC)": "02/13/2026 10:57:16",
  "Article name": "Getting Started Guides",
  "Article id": "24a4e58a-9d64-453c-8f00-4159eb94b82e",
  "Language": "English",
  "Project version id": "0644e0fe-2ae1-4d16-a993-98b81524d5c4",
  "Language code": "en",
  "Slugs": [
    { "language": "en", "slug": "getting-started" },
    { "language": "fr", "slug": "demarrer" },
    { "language": "de", "slug": "erste-schritte" }
  ]
}

```

A few details to plan for:

* `Event` holds the event's display name. Fields that don't apply to an event are left out, and non-article events carry their own fields, such as `Extension`, `Widget setting name` or `Folder name`.
* `Logged on (UTC)` isn't ISO 8601. The example above puts the month first, so parse it with an explicit `MM/DD/YYYY HH:mm:ss` format.
* `Slugs`, on delete events, lists the article's slug in every language, which is what you need to purge a cache.
* The docs' other sample payload names its event "Article settings saved", while the event list calls it "Article settings updated". Match event names against real deliveries, not the docs.

> **Inspect Document360 webhooks as they arrive.** Point a Webhook channel at a [Hookdeck Inspect](https://inspect.hookdeck.com) URL to see each event's exact headers and body, with no account or setup required.

## Setting up Document360 webhooks

Choose a header name and a long random value first, for example `Authorization` and `Bearer ` followed by the output of `openssl rand -hex 32`.

1. Go to **Settings** > **Knowledge base portal**, select **Notifications**, and on the **Channels** tab click **New channel**.
2. Select **Webhook**.
3. Enter a **Friendly name**, such as `Cache-ArticleDeletes`.
4. Choose a **Request method**, `POST` or `PUT`.
5. Enter your HTTPS endpoint in **Webhook URL**, and choose the **Request content** format.
6. Click **Add** under **Authorization key** and add your header as a key-value pair. Document360 doesn't say which header its separate **Authorization key** field fills in, so use a key-value header you name yourself.
7. Click **Save**.
8. Open the **Notification mapping** tab, turn on the events you need, and map them to the channel with **Map to channel**.

To route through Hookdeck, create a source with the **Document360** type and use its URL as the webhook URL. Enter the header's name in **API Key Header** and its exact value, including any `Bearer ` prefix, in **API Key**. The source has `POST` selected, so if you chose `PUT` on the channel, allow `PUT` in the source's allowed HTTP methods as well.

## Securing Document360 webhooks

Read the header you added, case-insensitively, and compare its whole value with the one you stored, in constant time. Accept both methods if you're not sure which one the channel uses.

```javascript
const crypto = require('crypto');
const express = require('express');

const HEADER = (process.env.DOCUMENT360_WEBHOOK_HEADER || 'Authorization').toLowerCase();
const SECRET = process.env.DOCUMENT360_WEBHOOK_SECRET; // the exact, whole header value

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

function handleDocument360(req, res) {
  const received = req.get(HEADER);
  if (!SECRET || typeof received !== 'string' || !safeEqual(received, SECRET)) {
    return res.sendStatus(401);
  }

  let event;
  try {
    event = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }

  // event['Event'] is the display name, e.g. "Article deleted".
  res.sendStatus(200);
}

const app = express();

app
  .route('/webhooks/document360')
  .post(express.raw({ type: '*/*' }), handleDocument360)
  .put(express.raw({ type: '*/*' }), handleDocument360);

```

The same check in Python:

```python
import hmac
import os

HEADER = os.environ.get("DOCUMENT360_WEBHOOK_HEADER", "Authorization")
SECRET = os.environ.get("DOCUMENT360_WEBHOOK_SECRET", "")

def verify_document360_request(headers) -> bool:
    if not SECRET:
        return False  # fail closed when unconfigured
    received = headers.get(HEADER)  # Starlette and Flask headers are case-insensitive
    if not received:
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(received.encode("utf-8"), SECRET.encode("utf-8"))

```

A static header authenticates the sender, not the body. It can't detect a modified payload, and with no signed timestamp a captured request can be replayed and still pass. Serve the endpoint over HTTPS only, use a long random value, and replace it on the channel and in your receiver if it might have leaked.

> **Make Document360 webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) checks your secret header on every Document360 event, stores each one durably, and lets you replay any delivery after a fix.

## Document360 webhook limitations and pain points

### No signature

**The Problem:** Anyone who learns the header value can send convincing events, and a changed body goes unnoticed.

**Why It Happens:** Document360 computes nothing over the body. The header you add is sent unchanged on every request.

**Workarounds:**

* Use a long random value, HTTPS only, and keep the header out of your logs.
* Before a destructive action, such as purging content, confirm the change through the Document360 API or portal.

**How Hookdeck Can Help:** Hookdeck's Document360 source compares the header you name with the exact value you enter. Hookdeck answers Document360 with a `200` either way, but a request with a missing or wrong value is recorded as failed verification and never reaches your destination.

### Events stop without warning

**The Problem:** Notifications you relied on stop arriving.

**Why It Happens:** Deleting a channel means "any events mapped to it silently stop delivering". An event switched on without a mapped channel never sends anything, and some actions don't fire the event you'd expect: an article created through **Word import** doesn't send Article created.

**Workarounds:**

* Map the **Notification channel deleted** event to a channel you watch.
* After changing mappings, check the **Notification history** tab.

**How Hookdeck Can Help:** Hookdeck's request log shows when events last arrived from Document360, and Hookdeck [issues](/docs/issues) can notify you about failed deliveries to your service.

### Delivery behaviour is undocumented

**The Problem:** You can't tell whether an event sent during your outage will arrive later.

**Why It Happens:** Document360 doesn't document a timeout or retry policy for webhook channels.

**Workarounds:**

* Respond with `200` as soon as the header checks out, and do the work afterwards.
* After an outage, review the Notification history and re-sync from the API.

**How Hookdeck Can Help:** Hookdeck accepts each event as soon as it arrives, stores it, and delivers it to your service with [retries](/docs/retries) you configure, so a short outage on your side doesn't depend on Document360 retrying.

### Display-label payloads

**The Problem:** Parsers break on keys with spaces, a field is missing, or a date is read the wrong way round.

**Why It Happens:** Keys are labels like `"Logged on (UTC)"`, fields vary by event, and the date format puts the month first.

**Workarounds:**

* Map the payload to your own field names in one place, and treat every field except `Event` as optional.
* Parse dates with an explicit format.

**How Hookdeck Can Help:** Hookdeck [transformations](/docs/transformations) can rename keys and normalise dates before events reach your service, and [filters](/docs/filters) can route events by their `Event` value.

## Best practices

### Scope channels by purpose

Document360 recommends scoping each channel to a focused set of events. A channel for cache purges and another for workflow automation are easier to debug than one channel for everything.

### Store the full header value

Keep the exact value, prefix included, as one string and compare it whole. Put the name and value in configuration, not code.

### Deduplicate on the event, object and time

There is no event ID. Build a key from `Event`, the object's ID (such as `Article id`) and `Logged on (UTC)`, and store it before acting. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Acknowledge fast and process asynchronously

Check the header, store the event, return `200`, and purge caches or call other APIs afterwards. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

Document360 sends knowledge base events through a Webhook notification channel, as JSON with display-label keys, once you map events to the channel, authenticated only by a header you add. Use a key-value header you name yourself, compare it whole in constant time, accept the method your channel uses, and watch for channels being deleted, which stops their events without warning.

[Hookdeck Event Gateway](https://hookdeck.com) sits in front of your receiver, checks the header, and keeps every event until your service has processed it.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Document360 webhooks reliably in minutes.