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

# Webhooks

> Receive signed workspace events and process deliveries safely.

Webhooks send workspace events to your integration's HTTP endpoint. You need `webhooks:manage` to manage subscriptions and inspect delivery history.

## Create a subscription

Use [Create a webhook subscription](/api-reference/webhooks/createWebhook) to select your destination and event types. Store the webhook secret securely. Use [Send a test delivery](/api-reference/webhooks/testWebhook) to verify your handler, then inspect [recent deliveries](/api-reference/webhooks/listWebhookDeliveries).

## Verify each request

Protodesk sends these headers:

| Header | Meaning |
| - | - |
| `Protodesk-Event-Id` | Stable event identifier for deduplication |
| `Protodesk-Event-Type` | Event type |
| `Protodesk-Timestamp` | Delivery timestamp in RFC 3339 format |
| `Protodesk-Signature` | `v1=` followed by a hexadecimal HMAC-SHA256 signature |

The signed message is the timestamp, a period, and the **raw request body**. Verify it before parsing or processing the payload. Re-serializing JSON changes the bytes and can invalidate the signature.

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

export function verifyWebhook(rawBody, headers, secret) {
  const timestamp = headers["protodesk-timestamp"];
  const signature = headers["protodesk-signature"];
  if (typeof timestamp !== "string" || typeof signature !== "string") return false;
  if (!/^v1=[a-f0-9]{64}$/i.test(signature)) return false;

  const deliveredAt = Date.parse(timestamp);
  // Five minutes is this example's policy; choose a window for your receiver.
  if (!Number.isFinite(deliveredAt) || Math.abs(Date.now() - deliveredAt) > 300_000) return false;

  const expected = createHmac("sha256", secret)
    .update(timestamp + ".")
    .update(rawBody)
    .digest();
  const received = Buffer.from(signature.slice(3), "hex");
  return received.length === expected.length && timingSafeEqual(received, expected);
}
```

Pass a raw `Buffer` and lowercase header names to this example. Configure your framework to retain the original body bytes. Never log the signing secret.

## Process events once

After verification, record `Protodesk-Event-Id` with a uniqueness constraint and enqueue your work durably. Return a successful response after accepting the event. Repeated deliveries of an already accepted event should succeed without repeating the business action.

Design your handler for retries and out-of-order events. When needed, retrieve the current resource through the API instead of relying on an older event snapshot.

## Troubleshoot deliveries

Check delivery history for your endpoint's response and failure details. Fix authentication, certificate, timeout, or handler errors before retrying. Persistent failures can disable a subscription; inspect its status and re-enable it after correcting the receiver.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.