Agent skill

Plane Webhooks Skill

Receive and verify Plane webhooks. Use when setting up Plane webhook handlers, debugging X-Plane-Signature verification, or handling project management events like workitem.created, workitem.updated, workitem.comment.created, project.updated, or cycle.created.

Install this skill

npx skills add hookdeck/webhook-skills --skill plane-webhooks


When to Use This Skill

  • How do I receive Plane webhooks?
  • How do I verify the X-Plane-Signature header?
  • Why is my Plane webhook signature verification failing?
  • How do I handle workitem.created, workitem.updated, workitem.comment.created, project.updated or cycle.created events from Plane?
  • How do I deduplicate Plane webhook deliveries (event_id vs delivery_id)?

Verification (core)

Plane signs the raw request body with HMAC-SHA256 keyed on the webhook secret key (plane_wh_..., used as plain UTF-8 bytes). X-Plane-Signature is the bare hex digest: no prefix, and nothing else (no timestamp or ID) is signed. Check the header is exactly 64 hex characters, then compare in constant time. Reject a missing, malformed or mismatched signature with 401.

Node:

const crypto = require('crypto');

function verify(rawBody, signature, secret) {
  if (typeof signature !== 'string' || !/^[0-9a-fA-F]{64}$/.test(signature) || !secret) return false;
  const expected = crypto.createHmac('sha256', Buffer.from(secret, 'utf8')).update(rawBody).digest();
  const received = Buffer.from(signature.toLowerCase(), 'hex'); // safe: format validated above
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

Python:

import hmac, hashlib, re

def verify(raw_body: bytes, signature: str | None, secret: str | None) -> bool:
    if not signature or not secret or not re.fullmatch(r"[0-9a-fA-F]{64}", signature, re.ASCII):
        return False
    expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature.lower().encode("ascii"), expected.encode("ascii"))

rawBody must be the exact bytes received (a Buffer, not a parsed or re-serialized object). In Express use express.raw({ type: '*/*' }); in Next.js use await request.arrayBuffer(); in FastAPI use await request.body(). Parse the JSON only after the check passes.

For complete handlers with route wiring, event dispatch, and tests, see:

Common Event Types

Dispatch on the body's event field (dot notation); the same value is sent in the X-Plane-Event header.

EventDescription
workitem.createdA work item is created
workitem.updatedA work item is updated (previous_attributes has the old values)
workitem.deletedA work item is deleted (data is {})
workitem.comment.createdA comment is added to a work item
project.created / project.updatedA project is created or updated
cycle.created / cycle.updatedA cycle is created or updated

Plane documents 43 events across projects, cycles, modules, milestones, pages, page comments, work items, comments, links, votes, attachments, relations, dependencies and page links. See references/overview.md for the full list.

Full event reference: Plane webhooks

Important Headers

HeaderDescription
X-Plane-SignatureHex HMAC-SHA256 of the raw body (no prefix)
X-Plane-EventEvent type, e.g. workitem.created (matches event in the body)
X-Plane-DeliveryUUID for this delivery attempt (matches delivery_id in the body)

Delivery and Retries

  • Any 2xx is a success. A 5xx or unreachable endpoint is retried with exponential backoff (about 10 minutes base, with jitter). After 5 failed attempts Plane disables the webhook and emails its creator.
  • 4xx responses are not retried, so a 401 for a bad signature ends that delivery.
  • event_id is stable across retries (deduplicate on it); delivery_id is new for each attempt.
  • No timestamp is signed, so there is no replay window to check.

Environment Variables

PLANE_WEBHOOK_SECRET=plane_wh_your_secret_here   # The webhook's secret key, from the CSV download or the edit form

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 plane --path /webhooks/plane

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 13, 2026

View on GitHub →