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

# Setting up webhooks

> Receive real-time HTTP notifications when events happen in Plane.

Instead of polling the API for changes, you can subscribe to webhooks and receive HTTP POST requests when events happen—a worker is created, a payment completes, a leave request is submitted, and more.

This guide walks you through configuring a webhook endpoint and handling your first event.

## How webhooks work

1. You register an HTTPS endpoint in Plane.
2. You select which events you want to receive.
3. When a matching event occurs, Plane sends a signed HTTP POST to your endpoint.
4. Your server verifies the signature and processes the event.

Plane uses [Svix](https://www.svix.com) for reliable webhook delivery, including automatic retries and a monitoring dashboard.

## Configure your endpoint

<Steps>
  <Step title="Navigate to webhook settings">
    In your Plane dashboard, go to **Developers > Webhooks**. You need admin access.
  </Step>

  <Step title="Add an endpoint">
    Click **Add Endpoint** and enter the URL where you want to receive events. This must be a publicly accessible HTTPS URL.
  </Step>

  <Step title="Select events">
    Choose which event types to subscribe to. You can start with all events or pick specific types like `payment.created` or `worker.updated`.
  </Step>

  <Step title="Save and copy your signing secret">
    After saving, copy the signing secret. You need this to verify that incoming webhooks are genuinely from Plane.

    <Warning>
      Store your signing secret securely. Do not commit it to version control.
    </Warning>
  </Step>
</Steps>

## Handle incoming webhooks

When an event fires, Plane sends a POST request with a JSON payload:

```json Example payload theme={null}
{
  "type": "payment.created",
  "data": {
    "id": "pt_ogdEjSnSqNYxtW1sd3pC3hFX",
    "amount": "1000.00",
    "currency": "USD",
    "date": "2024-01-15",
    "status": "pending",
    "worker": "wr_xyz789",
    "workspace": "ws_def456"
  }
}
```

## Verify the signature

Every webhook includes headers for signature verification. Always verify signatures before processing events in production.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { Webhook } from "svix";
  import express from "express";

  const app = express();

  app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
    const wh = new Webhook("whsec_your_signing_secret");

    try {
      const event = wh.verify(req.body, {
        "svix-id": req.headers["svix-id"],
        "svix-timestamp": req.headers["svix-timestamp"],
        "svix-signature": req.headers["svix-signature"],
      });

      console.log("Received event:", event.type);

      // Process the event based on type
      switch (event.type) {
        case "payment.created":
          // Handle new payment
          break;
        case "worker.updated":
          // Handle worker update
          break;
      }

      res.status(200).send("OK");
    } catch (err) {
      console.error("Verification failed:", err.message);
      res.status(400).send("Invalid signature");
    }
  });

  app.listen(3000);
  ```

  ```python Python theme={null}
  from svix.webhooks import Webhook
  from flask import Flask, request

  app = Flask(__name__)

  @app.route("/webhook", methods=["POST"])
  def handle_webhook():
      wh = Webhook("whsec_your_signing_secret")

      try:
          event = wh.verify(request.get_data(), request.headers)
      except Exception:
          return "Invalid signature", 400

      print(f"Received event: {event['type']}")

      if event["type"] == "payment.created":
          # Handle new payment
          pass
      elif event["type"] == "worker.updated":
          # Handle worker update
          pass

      return "OK", 200
  ```
</CodeGroup>

<Tip>
  Install the Svix library for your language: `npm install svix` (Node.js) or `pip install svix` (Python).
</Tip>

## Best practices

<AccordionGroup>
  <Accordion title="Respond quickly">
    Return a `2xx` status within 30 seconds. If your processing takes longer, acknowledge receipt immediately and handle the event asynchronously with a queue or background job.
  </Accordion>

  <Accordion title="Handle duplicates">
    Webhooks may be delivered more than once. Use the `svix-id` header to deduplicate events and make your processing idempotent.
  </Accordion>

  <Accordion title="Test in a sandbox">
    Configure webhooks inside a [sandbox](/reference/sandboxes) first. Sandbox webhooks deliver events for test activity only, so you can validate your handler without affecting live data.
  </Accordion>
</AccordionGroup>

## Monitor delivery

Plane's webhook dashboard (powered by Svix) shows you:

* All sent webhooks with payloads and response codes
* Failed deliveries with retry status
* Endpoint health and success rates

Access the dashboard from **Developers > Webhooks** in your Plane dashboard.

## Next steps

<CardGroup cols={2}>
  <Card title="Events reference" icon="bolt" href="/reference/events/object">
    See every event type Plane can send.
  </Card>

  <Card title="Webhooks reference" icon="book" href="/reference/webhooks">
    Full webhook documentation including retry behavior and payload structure.
  </Card>
</CardGroup>
