> ## Documentation Index
> Fetch the complete documentation index at: https://docs.neudocs.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive signed upload, approval, and request completion events.

NeuDocs can POST signed JSON events to an HTTPS endpoint controlled by your firm. Use webhooks to update a CRM or relay client notifications through Zapier, Make, n8n, or your own service.

Configure endpoints at **Settings → Integrations**. Only owners and admins can manage them.

## Set up an endpoint

1. Enter a public `https://` endpoint URL. Private, loopback, and internal addresses are rejected when saved and before every delivery.
2. Select at least one event.
3. Optionally enable **Recipient details** for client-facing events.
4. Copy the signing secret immediately. It is displayed once; create a new endpoint if it is lost.
5. Use **Send test** to fire a `ping` event and check the recorded delivery status.

## Events

| Event               | Fires when                                     | Recipient details |
| ------------------- | ---------------------------------------------- | ----------------- |
| `request.sent`      | A request is sent or re-sent.                  | Available         |
| `reminder.sent`     | A reminder is sent for outstanding items.      | Available         |
| `comment.replied`   | Staff replies to a client on a request.        | Available         |
| `request.completed` | Every required item is approved.               | Not included      |
| `item.approved`     | Staff approves one request item.               | Not included      |
| `upload.received`   | A client upload is accepted into the workflow. | Not included      |
| `ping`              | An endpoint test is sent.                      | Not included      |

## Payload and headers

Every delivery uses the same envelope:

```json theme={null}
{
  "event": "reminder.sent",
  "data": {
    "requestId": "req_123",
    "missingItemCount": 3,
    "channels": ["email"]
  },
  "timestamp": "2026-07-30T12:00:00.000Z"
}
```

```text theme={null}
Content-Type: application/json
X-NeuDocs-Event: reminder.sent
X-NeuDocs-Signature: sha256=<hex digest>
```

Event data can include:

```jsonc theme={null}
// request.sent — initial send
{ "requestId": "…", "clientId": "…", "itemCount": 4, "channels": ["email"] }

// request.sent — re-send
{ "requestId": "…", "resent": true, "channels": ["email"] }

// reminder.sent
{ "requestId": "…", "missingItemCount": 3, "channels": ["email"] }

// comment.replied
{ "requestId": "…", "channels": ["email"] }

// request.completed
{ "requestId": "…" }

// item.approved
{ "requestId": "…", "itemId": "…" }

// upload.received
{ "requestId": "…", "itemId": "…", "contentType": "application/pdf", "sizeBytes": 182734 }
```

`channels` reports where NeuDocs delivered the client message. It is currently `["email"]`.

## Recipient details

Recipient details are off by default. When enabled, `request.sent`, `reminder.sent`, and `comment.replied` add this object:

```json theme={null}
{
  "recipient": {
    "email": "client@example.com",
    "name": "Ada Lovelace",
    "portalUrl": "https://app.neudocs.com/c/pLZ3…"
  }
}
```

<Warning>
  `portalUrl` is a credential. Anyone holding it can open that client's upload page for 72 hours without signing in. A newer link for the request invalidates it sooner. Send it only to trusted HTTPS endpoints and never log it.
</Warning>

Without the option, payloads contain IDs only. NeuDocs never includes phone numbers, document contents, filenames, extracted fields, or the signing secret.

## Verify the signature

Compute HMAC-SHA256 over the **exact raw request body bytes** using the endpoint secret. Do not parse and re-serialize the body before verification.

```js theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

export function validNeuDocsSignature(rawBody, header, secret) {
  const expected = `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`;
  const actual = Buffer.from(header ?? "");
  const wanted = Buffer.from(expected);
  return actual.length === wanted.length && timingSafeEqual(actual, wanted);
}
```

Reject an invalid signature before acting on the event.

## Delivery semantics

* Delivery is best-effort, with no retries.
* Event ordering is not guaranteed. Make handlers idempotent and key work by `requestId`.
* A failed endpoint never blocks another endpoint or the underlying NeuDocs action.
* Return `2xx` quickly and process slow work asynchronously.

## Relay notifications to WhatsApp

NeuDocs does not connect to WhatsApp natively. Your firm supplies the relay through Zapier, Make, n8n, the WhatsApp Cloud API, Twilio, or another service.

NeuDocs does not store client phone numbers, so your relay must look one up in your CRM using `recipient.email`. You also need your own WhatsApp Business setup, approved message templates when required, and documented recipient opt-in.

1. Subscribe an endpoint to `request.sent` and `reminder.sent`.
2. Enable **Recipient details** and copy the signing secret.
3. Verify every signature.
4. Look up the phone number in your CRM by recipient email.
5. Send an approved template using the recipient name and outstanding item count.

For lower exposure, send a nudge telling the client to check email instead of including `portalUrl` in a forwardable chat. If your phone records exist only in NeuDocs, register interest under **Settings → Notifications → Delivery channels → WhatsApp**; native delivery is not currently available.

## Troubleshooting

| Symptom                           | Likely cause                                                           |
| --------------------------------- | ---------------------------------------------------------------------- |
| Endpoint rejected when saved      | The URL is not HTTPS or resolves to a private or loopback address.     |
| No event arrives                  | The endpoint is not subscribed to that event.                          |
| `recipient` is missing            | Recipient details are disabled or the event is not client-facing.      |
| Signature does not match          | The HMAC was computed over re-serialized JSON instead of the raw body. |
| Last delivery shows an HTTP error | The receiver rejected it; failed deliveries are not retried.           |


## Related topics

- [Troubleshooting](/reference/troubleshooting.md)
- [Integrations overview](/integrations/overview.md)
- [Notifications](/workspace/notifications.md)
- [Review and completion](/workflow/review.md)
- [Settings and branding](/workspace/settings.md)
