# Document360 Webhooks

Document360's **Webhook notification channel** sends a JSON payload to your URL when mapped
knowledge base events happen: an article is published, created or deleted, a workflow status
changes, an extension token is deleted. Webhooks are **not signed**. You authenticate them with a
secret header that you add to the channel as a key-value pair; your endpoint compares that header
with the value you stored.

## When to Use This Skill

* How do I receive Document360 webhooks?
* Does Document360 sign webhook payloads? (No: use a secret header you add to the channel)
* How do I check the header I set on a Document360 Webhook channel?
* How do I handle `Article published`, `Article created` or `Article deleted` events?
* Why does my Document360 webhook endpoint return 401 or reject PUT requests?

## Verification (core)

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

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyDocument360Request(
  headers,                                                    // Node lowercases header names
  secret = process.env.DOCUMENT360_WEBHOOK_SECRET,            // the WHOLE header value
  name = process.env.DOCUMENT360_WEBHOOK_HEADER || 'Authorization'
) {
  if (!secret) return false;                                  // never accept when unconfigured
  const received = headers[name.toLowerCase()];
  return typeof received === 'string' && received !== '' && safeEqual(received, secret);
}

```

* The header **name is yours**: it is whatever key you typed in the channel. Read it from
  `DOCUMENT360_WEBHOOK_HEADER` (default `Authorization`) and look it up case-insensitively.
* Compare the **whole value**, exactly as typed, including any `Bearer ` prefix. Don't strip or parse it.
* Python: `hmac.compare_digest` on **bytes** (encode both sides, or compare SHA-256 digests); passing
  `str` raises `TypeError` on non-ASCII input.
* Return `401` for a missing or mismatched header, `400` for a non-empty body that isn't a JSON
  object, `200` once handled. An authenticated request with an empty body gets `200`.
* Read the body raw (`express.raw({ type: '*/*' })`) and authenticate before parsing it.
* Accept **POST and PUT**: the channel's **Request method** dropdown offers both. In Next.js export
  a handler for each; in FastAPI use `methods=["POST", "PUT"]`.

> **A static shared credential authenticates the sender, not the body, and gives no replay
> protection** (no signature, no timestamp). Anyone who captures one request can resend it. Use
> HTTPS, keep the value secret, and rotate it in both Document360 and your environment if it leaks.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/document360-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/document360-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/document360-webhooks/examples/fastapi/).

## Event Types

The payload's `Event` field carries the event's display name. Keys are display labels with spaces.

| Event | Sent When |
| --- | --- |
| `Article published` | An article is published |
| `Article created` | A new article is created |
| `Article deleted` | An article is deleted (payload adds `Slugs`, one per language) |
| `Article settings saved` | Article settings are saved (name used in the docs' sample payload) |
| `Article workflow status updated` | An article's workflow status changes |
| `Extension token deleted` | An extension token is deleted (payload has `Extension`) |

These are a subset. The full list of events you can map is in Document360's
[notification mapping](https://docs.document360.com/docs/notification-mapping) docs (categories, Drive,
workspaces, site settings and more). Return `200` for events you don't handle too; Document360
doesn't document whether or how it retries failed deliveries.

Article event payload (the docs' example):

```json
{
  "Knowledge base name": "Document360 2.0",
  "Event": "Article settings saved",
  "User": "Matt Sterling",
  "User email": "matt.sterling@document360.co",
  "Logged on (UTC)": "09/12/2025 06:15:00",
  "Article name": "Getting Started",
  "Article id": "e1444f0x-0xx0-000x-xxx0-0x3f2bc3713c",
  "Language": "English",
  "Language code": "en",
  "Project version id": "55144000-0xxx-000x-0000-0x2346b3bms3",
  "Project version name": "1.0"
}

```

## Environment Variables

```bash
DOCUMENT360_WEBHOOK_HEADER=Authorization   # the header key you added to the channel
DOCUMENT360_WEBHOOK_SECRET=your-secret     # its exact, whole value (including any "Bearer ")

```

## Using Hookdeck

Hookdeck's **Document360** source type authenticates this the same way: you give it the header
name and the exact whole value. The source **pre-selects POST without managing the method**, so if
your channel's Request method is PUT, also allow PUT on the source.

## Local Development

Document360 can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 document360 --path /webhooks/document360

```

Use the printed URL as the **Webhook URL** in the channel, and add the same header there.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/document360-webhooks/references/overview.md) - What Document360 webhooks are, events and payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/document360-webhooks/references/setup.md) - Create the Webhook channel, add the header, map events
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/document360-webhooks/references/verification.md) - Header check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/document360-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests