# How to Configure Custom Webhooks for Project and Issue Events in Plane

> Learn to configure custom webhooks for project and issue events in Plane. Easily set up real-time notifications for your development workflow via the API or UI.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: how-to-guide
- Published: 2026-06-22

---

**You configure custom webhooks in Plane by creating a `Webhook` record via the REST API or Workspace Settings UI, enabling the boolean flags for `project` and `issue` events, and providing a target URL where Plane will POST event payloads signed with an auto-generated secret key.**

Plane exposes a built-in webhook system that notifies external services whenever projects or issues are created, updated, or deleted. According to the Plane source code, the system uses Django models to persist configuration, a React-based settings UI for management, and asynchronous background tasks to deliver events reliably.

## Understanding the Webhook Data Model

Plane stores webhook definitions in the **`Webhook`** model located at [`apps/api/plane/db/models/webhook.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/webhook.py). Each record encapsulates the target URL, authentication secret, and event subscription flags.

### Event Type Configuration

The `Webhook` model defines boolean fields for each event category. To receive notifications for project and issue activity, you enable the corresponding flags:

- **`project`** – Triggers when projects are created, updated, or archived.
- **`issue`** – Triggers when issues are created, updated, deleted, or moved.
- **`module`** – Optional module events.
- **`cycle`** – Optional cycle events.
- **`issue_comment`** – Optional comment events.

When you create a webhook, Plane automatically generates a **`secret_key`** using the `generate_token()` function. This secret appears in the UI and is used to sign every outgoing request via the `X-Plane-Webhook-Signature` header.

## Configuring Webhooks Through the UI

The front-end interface for webhook management lives in `apps/web/app/(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx`. This React component consumes the `useWebhook` hook, which wraps the `WebhookService` from [`packages/services/src/developer/webhook.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/developer/webhook.service.ts).

Follow these steps to configure project and issue webhooks:

1. **Navigate to Workspace Settings** → **Webhooks**.
2. Click **"Add webhook"** to open the creation modal.
3. Enter your **Webhook URL** (the HTTPS endpoint that will receive POST requests).
4. Check the **Project** and **Issue** event boxes to subscribe to those event types.
5. Save the webhook. Plane immediately begins delivering events to your URL.

The UI displays the auto-generated secret key after creation. You can regenerate this secret at any time by clicking the **"Regenerate secret"** button, which calls `POST /api/workspaces/:workspaceSlug/webhooks/:webhookId/regenerate/`.

## Managing Webhooks via the REST API

Plane exposes a full CRUD API under the workspace namespace. All endpoints require authentication via Bearer token.

### Available Endpoints

| Method | Endpoint | Purpose |
|--------|----------|---------|
| `GET` | `/api/workspaces/:workspaceSlug/webhooks/` | List all webhooks |
| `POST` | `/api/workspaces/:workspaceSlug/webhooks/` | Create new webhook |
| `GET` | `/api/workspaces/:workspaceSlug/webhooks/:webhookId/` | Retrieve specific webhook |
| `PATCH` | `/api/workspaces/:workspaceSlug/webhooks/:webhookId/` | Update webhook |
| `DELETE` | `/api/workspaces/:workspaceSlug/webhooks/:webhookId/` | Delete webhook |
| `POST` | `/api/workspaces/:workspaceSlug/webhooks/:webhookId/regenerate/` | Rotate secret key |

### Creating a Webhook with cURL

To create a webhook that listens for project and issue events via the API:

```bash
curl -X POST "https://api.plane.so/api/workspaces/acme/webhooks/" \
  -H "Authorization: Bearer <YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
        "url": "https://example.com/plane-webhook",
        "project": true,
        "issue": true,
        "module": false,
        "cycle": false,
        "issue_comment": false
      }'

```

### Using the TypeScript Service

If you are building a custom integration or extending the Plane frontend, use the `WebhookService` class:

```typescript
import WebhookService from "@plane/services/developer/webhook.service";

const webhookService = new WebhookService();

async function createProjectIssueWebhook(workspaceSlug: string) {
  const payload = {
    url: "https://example.com/plane-webhook",
    project: true,
    issue: true,
    module: false,
    cycle: false,
    issue_comment: false,
  };

  const webhook = await webhookService.create(workspaceSlug, payload);
  console.log("Webhook created:", webhook.id, webhook.secret_key);
  return webhook;
}

```

The service provides methods for `list`, `create`, `update`, `destroy`, and `regenerateSecretKey`, matching the REST API capabilities.

## Securing and Verifying Webhook Payloads

Plane signs every outgoing webhook request to prevent tampering. The signature appears in the `X-Plane-Webhook-Signature` header as an HMAC-SHA256 hex digest prefixed with `sha256=`.

Verify the payload on your receiver endpoint using the secret key Plane generated:

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

function verifyPlaneWebhook(req, webhookSecret) {
  const signature = req.headers['x-plane-webhook-signature'];
  if (!signature || !signature.startsWith('sha256=')) {
    return false;
  }
  
  const hmac = crypto.createHmac('sha256', webhookSecret);
  hmac.update(JSON.stringify(req.body));
  const expected = `sha256=${hmac.digest('hex')}`;
  
  return crypto.timingSafeEqual(
    Buffer.from(signature), 
    Buffer.from(expected)
  );
}

```

Always validate the signature before processing the payload to ensure the request originated from your Plane workspace.

## Backend Delivery and Logging

When a project or issue event occurs, Plane creates a **`WebhookLog`** entry (defined in [`apps/api/plane/db/models/webhook.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/webhook.py) around line 65) to track delivery attempts. The actual HTTP POST is handled asynchronously by [`apps/api/plane/bgtasks/webhook_task.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/bgtasks/webhook_task.py).

This background task:
1. Serializes the event data (project or issue snapshot).
2. Adds the `X-Plane-Webhook-Signature` header.
3. POSTs to your configured URL.
4. Records the HTTP response status and any errors in the `WebhookLog` for retry logic.

Failed deliveries are retried automatically based on the webhook log state, ensuring reliable event transmission even if your endpoint experiences temporary downtime.

## Summary

- **Storage**: Webhooks are stored in the `Webhook` model at [`apps/api/plane/db/models/webhook.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/webhook.py) with boolean flags for `project`, `issue`, and other event types.
- **Configuration**: Use the Workspace Settings UI ([`webhooks/page.tsx`](https://github.com/makeplane/plane/blob/main/webhooks/page.tsx)) or the REST API endpoints under `/api/workspaces/:workspaceSlug/webhooks/` to create and manage webhooks.
- **Security**: Each webhook has an auto-generated `secret_key` used to sign payloads via the `X-Plane-Webbook-Signature` header.
- **Delivery**: Asynchronous tasks in [`apps/api/plane/bgtasks/webhook_task.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/bgtasks/webhook_task.py) handle the actual HTTP delivery and maintain logs in the `WebhookLog` model.

## Frequently Asked Questions

### How do I enable webhooks for only project events and not issue events?

Set the `project` field to `true` and the `issue` field to `false` when creating or updating the webhook. In the UI, uncheck the **Issue** checkbox while keeping **Project** checked. In the API, send `"project": true` and `"issue": false` in the JSON payload.

### Where does Plane store the webhook secret and how is it generated?

The secret is stored in the `secret_key` field of the `Webhook` model. Plane generates it automatically using the `generate_token()` function when the webhook is created. You can view the secret in the Workspace Settings UI or trigger a regeneration via the `POST .../regenerate/` endpoint.

### What happens if my webhook endpoint returns an error or is unreachable?

Plane logs the attempt in the `WebhookLog` model (defined in [`apps/api/plane/db/models/webhook.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/db/models/webhook.py)). The background task in [`apps/api/plane/bgtasks/webhook_task.py`](https://github.com/makeplane/plane/blob/main/apps/api/plane/bgtasks/webhook_task.py) records the HTTP response status and implements retry logic to attempt redelivery of failed webhook events.

### Can I configure webhooks programmatically without using the Plane UI?

Yes. Use the `WebhookService` class from [`packages/services/src/developer/webhook.service.ts`](https://github.com/makeplane/plane/blob/main/packages/services/src/developer/webhook.service.ts) if working within the Plane ecosystem, or make direct HTTP requests to the REST API endpoints. Authenticate with a Bearer token and POST to `/api/workspaces/:workspaceSlug/webhooks/` with the appropriate event boolean flags.