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

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. 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.

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:

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:

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:

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 around line 65) to track delivery attempts. The actual HTTP POST is handled asynchronously by 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 with boolean flags for project, issue, and other event types.
  • Configuration: Use the Workspace Settings UI (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 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). The background task in 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →