Skip to content

Commit 3451029

Browse files
committed
chore: refactor the webhooks guide
DX-19
1 parent d7bd809 commit 3451029

File tree

5 files changed

+180
-101
lines changed

5 files changed

+180
-101
lines changed

docs/webhooks/overview.mdx

Lines changed: 176 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,217 @@
11
---
22
title: Webhooks
33
sidebar_label: Overview
4-
description: A webhook triggers a HTTP POST request on the specified url, whenever there is a change in an event. Like a new project is created, updated or deleted then a webhook can be triggered to receive the required payload.
4+
description: Use webhooks to receive real-time notifications about events in Plane.
55
---
66

77
{frontMatter.description && <h3 class="description">{frontMatter.description}</h3>}
88

9-
## Creating a webhook
9+
## Introduction
1010

11-
`url` You are required to provide a url in which you want the payloads to be triggered.
11+
Webhooks are a powerful way to receive real-time HTTP-based notifications about
12+
events in Plane. By setting up a webhook, you can automatically receive updates
13+
whenever specific events occur, such as the creation, update, or deletion of
14+
projects, work items, or other entities.
1215

13-
Then select the events for which you want the webhook to be triggered.
16+
:::tip Events
17+
Plane supports the following webhook events:
1418

15-
After you create the webhook, a secret key will be created automatically and will be downloaded in csv format.
19+
- **Project**: created, updated, deleted
20+
- **Cycle**: created, updated, deleted
21+
- **Module**: created, updated, deleted
22+
- **Work Item**: created, updated, deleted, added to cycle, added to module
23+
- **Work Item Comment**: created, updated, deleted
24+
:::
1625

17-
```json
18-
"Content-Type": "application/json",
19-
"User-Agent": "Autopilot",
20-
"X-Plane-Delivery": "f819eff4-cd50-4987-bc97-e5be1e04c94f",
21-
"X-Plane-Event": "project",
22-
"X-Plane-Signature": "7896ae9addb1f73931132b4f3e052bf12c410b837b24898e75dcd660c7"
2326

24-
```
27+
## Creating a webhook
2528

26-
| Header | Description |
27-
| ----------------- | -------------------------------------------------------------------- |
28-
| X-Plane-Delivery | It is a randomly generated UUID for uniquely identifying the payload |
29-
| X-Plane-Event | It describes the event for which the webhook triggered |
30-
| X-Plane-Signature | A signature is generated based on the secret and the payload |
29+
To create a webhook, navigate to your Plane workspace settings, **Developer**
30+
section, and click **Webhooks**. Then click the **Add webhook** button.
3131

32-
### Webhook Payload Example for the project `update`
32+
import ImgCreate from '@site/static/images/webhooks/create.png';
3333

34-
```json
35-
"event": "project",
36-
"action": "update",
37-
"webhook_id": "3c2c32ac-82df-48b3-be2a-a3e21dbe8692",
38-
"workspace_id": "d2d97c94-a6ad-4012-b526-5577c0d7c769",
39-
"data": {
40-
"id":"22b6fc9c-1849-45da-b103-52a3e3a6b4c1",
41-
"workspace_detail": {
42-
"name":"Testing Project",
43-
"slug":"testing-project",
44-
"id":"bob1b192-f988-4bf9-b569-825de8cb0678"
45-
},
46-
"created_at":"2023-10-25T04:38:59.566962Z",
47-
"updated_at":"2023-10-25T06:44:48.543685Z",
48-
"name":"vfecddcwerj",
49-
"description":"",
50-
"description_text":null,
51-
"description_html":null,
52-
"network":2,
53-
"identifier":"TRACE",
54-
"emoji":null,
55-
"icon_prop":null,
56-
"module_view":true,
57-
"cycle_view":true,
58-
"issue_views_view":true,
59-
"page_view":true,
60-
"inbox_view":true,
61-
"cover_image":null,
62-
"archive_in":0,
63-
"close_in":0,
64-
"created_by":"6bb20d1c-4960-41ca-af4f-cee01de160c4",
65-
"updated_by":"6bb20d1c-4960-41ca-af4f-cee01de160c4",
66-
"workspace":"bob1b192-f988-4bf9-b569-825de8cb0678",
67-
"default_assignee":null,
68-
"project_lead":null,
69-
"estimate":null,
70-
"default_state":null
71-
},
34+
<img src={ImgCreate} className='center' style={{width: '700px'}} alt="Create webhook" />
7235

73-
```
36+
Next, enter your URL, optionally choose the events which will trigger
37+
your webhook and click **Create**.
7438

75-
User can choose the things for which they want the webhook to be triggered.
39+
import ImgDetails from '@site/static/images/webhooks/details.png';
7640

77-
Currently Plane supports the following events for which webhook can be trigged:
41+
<img src={ImgDetails} className='center' style={{width: '700px'}} alt="Define webhook details" />
7842

79-
- Project
80-
- Issue
81-
- Cycle
82-
- Module
83-
- Issue Comment
43+
Afterwards, you will be prompted to save your webhook secret key. You can use
44+
this key to verify the authenticity of webhook payloads you receive from Plane.
45+
We'll cover this [later in the guide](#verifying-payloads).
8446

85-
## Verifying Signature
47+
import ImgKey from '@site/static/images/webhooks/key.png';
8648

87-
```python
88-
import hashlib
89-
import hmac
49+
<img src={ImgKey} className='center' style={{width: '700px'}} alt="Webhook key created" />
9050

91-
secret_token = os.environ.get("WEBHOOK_SECRET")
51+
## Defining a webhook consumer
9252

93-
received_signature = request.headers.get('X-Plane-Signature')
94-
received_payload = json.dumps(request.json).encode('utf-8')
53+
Your webhook consumer URL must be an HTTP endpoint which satisfies the following conditions:
9554

96-
expected_signature = hmac.new(secret_token.encode('utf-8'), msg=received_payload, digestmod=hashlib.sha256).hexdigest()
55+
1. Responds to Plane requests with an `HTTP 200` ("OK") response
56+
2. Is publicly accessible
57+
3. Is not localhost
58+
- Valid: `https://example.com/webhook`
59+
- Invalid: `http://localhost:3000/webhook`
9760

98-
if not hmac.compare_digest(expected_signature, received_signature):
99-
raise HTTPException(status_code=403, detail="Invalid Signature provided")
61+
:::tip
62+
If your consumer does not respond with an `HTTP 200` response, Plane will retry the delivery
63+
up to three times with an exponential backoff delay.
64+
:::
10065

101-
```
66+
## Webhook payloads
10267

103-
## How webhooks work
68+
When a webhook is triggered, Plane sends a JSON payload to your specified URL via HTTP `POST`.
69+
The payload contains the following fields:
10470

105-
Your webhook consumer is a simple HTTP endpoint. It must satisfy the following conditions:
71+
- `action`: One of `create`, `update`, or `delete`, indicating the type of event
72+
- `event`: One of `project`, `cycle`, `module`, or `issue`, indicating the type of event that triggered the webhook
73+
- _Note: `issue` refers to Work Items in Plane. This will be updated in the future to reflect the correct terminology._
74+
- `webhook_id`: The unique identifier of the webhook
75+
- `workspace_id`: The ID of the workspace where the event occurred
76+
- `data`: An optional object containing the details of the event. The structure
77+
of this field varies based on the `event` and `action` (see below).
10678

107-
- It's available in a publicly accessible non-localhost URL.
108-
- It will respond to the Plane Webhook push (HTTP POST request) with a `HTTP 200` ("OK") response.
79+
### Payload examples
10980

110-
If a delivery fails (i.e. server unavailable or responded with a non-200 HTTP status code), the push will be retried a couple of times. Here an exponential backoff delay is used: the attempt will be retried after approximately 10 minutes, then 30 minutes, and so on.
81+
#### Delete action
11182

112-
The webhooks are triggered for POST, PATCH, and DELETE requests.
83+
```json
84+
{
85+
"event":"issue",
86+
"action":"delete",
87+
"webhook_id":"f1a2fe64-c8d4-4eed-b3ef-498690052c1d",
88+
"workspace_id":"c467e125-59e3-44ec-b5ee-f9c1e138c611",
89+
"data":{
90+
"id":"9a28bd00-ed9c-4f5d-8be9-fc05cbb1fc57"
91+
}
92+
}
93+
```
11394

114-
- For DELETE requests, the response only includes the ID of the deleted entity.
95+
#### Update action
11596

97+
```json
98+
{
99+
"event":"project",
100+
"action":"update",
101+
"webhook_id":"3c2c32ac-82df-48b3-be2a-a3e21dbe8692",
102+
"workspace_id":"d2d97c94-a6ad-4012-b526-5577c0d7c769",
103+
"data": {
104+
"id":"22b6fc9c-1849-45da-b103-52a3e3a6b4c1",
105+
"workspace_detail": {
106+
"name":"Testing Project",
107+
"slug":"testing-project",
108+
"id":"bob1b192-f988-4bf9-b569-825de8cb0678"
109+
},
110+
"created_at":"2023-10-25T04:38:59.566962Z",
111+
"updated_at":"2023-10-25T06:44:48.543685Z",
112+
"name":"vfecddcwerj",
113+
"description":"",
114+
"description_text":null,
115+
"description_html":null,
116+
"network":2,
117+
"identifier":"TRACE",
118+
"emoji":null,
119+
"icon_prop":null,
120+
"module_view":true,
121+
"cycle_view":true,
122+
"issue_views_view":true,
123+
"page_view":true,
124+
"inbox_view":true,
125+
"cover_image":null,
126+
"archive_in":0,
127+
"close_in":0,
128+
"created_by":"6bb20d1c-4960-41ca-af4f-cee01de160c4",
129+
"updated_by":"6bb20d1c-4960-41ca-af4f-cee01de160c4",
130+
"workspace":"bob1b192-f988-4bf9-b569-825de8cb0678",
131+
"default_assignee":null,
132+
"project_lead":null,
133+
"estimate":null,
134+
"default_state":null
135+
}
136+
}
116137
```
117-
"action":"delete",
118-
"data":{
119-
"id":"9a28bd00-ed9c-4f5d-8be9-fc05cbb1fc57"
120-
},
121-
"event":"issue",
122-
"webhook_id":"f1a2fe64-c8d4-4eed-b3ef-498690052c1d",
123-
"workspace_id":"c467e125-59e3-44ec-b5ee-f9c1e138c611"
124138

125-
```
139+
## Webhook headers
126140

127-
- However, for both POST and PATCH requests, the complete payload is sent in the response.
141+
When Plane sends a webhook payload, it includes several HTTP headers to help you
142+
identify and verify the request. The most important headers are:
128143

144+
```json
145+
"Content-Type": "application/json",
146+
"X-Plane-Delivery": "f819eff4-cd50-4987-bc97-e5be1e04c94f",
147+
"X-Plane-Event": "project",
148+
"X-Plane-Signature": "7896ae9addb1f73931132b4f3e052bf12c410b837b24898e75dcd660c7"
129149
```
130-
"event":"issue",
131-
"action":"update",
132-
"webhook_id":"f1a2fe64-c8d4-4eed-b3ef-498690052c1d",
133-
"workspace_id":"c467e125-59e3-44ec-b5ee-f9c1e138c611",
134-
"data":{ ... }
135150

136-
```
151+
| Header | Description |
152+
| ----------------- | -------------------------------------------------------------------- |
153+
| X-Plane-Delivery | A randomly generated UUID for uniquely identifying the payload |
154+
| X-Plane-Event | The event for which the webhook was triggered |
155+
| X-Plane-Signature | A signature generated based on the secret and the payload |
156+
157+
158+
## Verifying payloads
159+
160+
To verify the authenticity of the webhook payloads, you can use the `X-Plane-Signature` header.
161+
This header contains an HMAC SHA-256 signature of the payload, generated using the secret key
162+
you received when creating the webhook. If the signature matches the expected signature, you can be confident that the payload
163+
was sent by Plane and has not been tampered with.
164+
165+
Here's how to verify the payload signature in Python:
166+
167+
import Tabs from '@theme/Tabs';
168+
import TabItem from '@theme/TabItem';
169+
170+
<Tabs>
171+
<TabItem value="NodeJS" label="NodeJS" default>
172+
```javascript
173+
// Fastify example
174+
import { createHmac } from 'node:crypto';
175+
const secret = process.env.WEBHOOK_SECRET;
176+
177+
// Fastify setup excluded for brevity
178+
179+
fastify.post('/webhook', options, async (request, reply) => {
180+
const receivedSignature = request.headers['x-plane-signature'];
181+
const payload = JSON.stringify(request.body);
182+
183+
const computedSignature = createHmac('sha256', secret)
184+
.update(payload)
185+
.digest('hex');
186+
187+
if (receivedSignature !== computedSignature) {
188+
return reply.status(403).send('Invalid signature');
189+
}
190+
191+
// Process the webhook payload
192+
return reply.status(200).send('Webhook received');
193+
})
194+
```
195+
</TabItem>
196+
<TabItem value="python" label="Python">
197+
```python
198+
import hashlib
199+
import hmac
200+
201+
secret_token = os.environ.get("WEBHOOK_SECRET")
202+
203+
received_signature = request.headers.get('X-Plane-Signature')
204+
received_payload = json.dumps(request.json).encode('utf-8')
205+
206+
expected_signature = hmac.new(secret_token.encode('utf-8'), msg=received_payload, digestmod=hashlib.sha256).hexdigest()
207+
208+
if not hmac.compare_digest(expected_signature, received_signature):
209+
raise HTTPException(status_code=403, detail="Invalid Signature provided")
210+
```
211+
</TabItem>
212+
</Tabs>
137213

138-
:::note
139-
Whenever an issue is added to the module, the corresponding issue webhook will be triggered. Similarly, any updates made to the cycle issue will also activate the issue webhook.
214+
:::tip Questions?
215+
If you have any questions or need help with webhooks, please reach out to us at
216+
140217
:::

src/css/custom.css

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ table thead tr {
758758
margin: 10px;
759759
}
760760

761-
img[src*='#center'] {
761+
img[src*='#center'], img.center {
762762
display: block;
763763
border-radius: 0.75rem;
764764
float: none;
@@ -771,7 +771,9 @@ img[src*='#center'] {
771771
background-color: rgb(226 226 226);
772772
}
773773

774-
[data-theme='dark'] img[src*='#center'] {
774+
[data-theme='dark'] img[src*='#center'],
775+
[data-theme='dark'] img.center
776+
{
775777
display: block;
776778
border-radius: 0.75rem;
777779
float: none;

static/images/webhooks/create.png

310 KB
Loading

static/images/webhooks/details.png

107 KB
Loading

static/images/webhooks/key.png

53.4 KB
Loading

0 commit comments

Comments
 (0)