Webhooks
Linear provides webhooks which allow you to receive HTTP push notifications whenever data is created or updated. This allows you to build integrations on top of Linear. You could trigger CI builds, perform calculations on issue data, or send messages on specific conditions – you name it.
Webhooks are specific to an
Organization
, but you can configure webhooks to provide updates from all public teams, or a single team to satisfy the needs of each team in your organization. Additionally, OAuth applications can configure webhook settings. Once those settings are configured, each time a new organization authorizes the given application, a webhook will be created for that organization that posts to the provided webhook URL, as described below. Please visit your application's settings to configure webhooks.
What we call "data change webhooks" are currently supported for the following models:
Issues
Issue comments
Issue labels
Comment reactions
Projects
Project updates
Cycles
Other webhooks are provided for convenience:
A Webhook push is simply a
HTTP POST
request, sent to the URL of your choosing. The push is automatically triggered by Linear when data updates. For an example of what data a payload contains, see The Webhook Payload.Your webhook consumer is a simple HTTP endpoint. It must satisfy the following conditions:
- It's available in a publicly accessible HTTPS, non-localhost URL
- It will respond to the Linear Webhook push (HTTP POST request) with a
HTTP 200
("OK") response
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. If the webhook URL continues to be unresponsive the webhook might be disabled by Linear, and must be re-enabled again manually.
To make sure a Webhook POST is truly created by Linear, you can check the request to originates from one of the following IPs: 35.231.147.226 or 35.243.134.228.
For additional information on Webhooks, there are a number of good resources:
You will first need to create a Webhook endpoint ("consumer") to be called by the Linear Webhook agent. This can be a simple HTTP server you deploy yourself, or a URL endpoint configured by a service such as Zapier (or for testing purposes, RequestBin).
Once your consumer is ready to receive updates, you can enable it for your Linear team. Webhooks can be enabled in Linear both via the Team Settings UI.
You might consider using something like Netlify Functions, which provides a straightforward way of deploying simple HTTP(S) endpoints: https://www.netlify.com/blog/2018/09/13/how-to-run-express.js-apps-with-netlify-functions/.
Keeping the requirements in mind, a simple but workable Node.js/Express (v4) webhook consumer might look something like this:
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
const port = 3000;
app.use(
express.json({
// Save raw body buffer before JSON parsing
verify: (req) => {
req.rawBody = buf;
},
})
);
// Parse the request body
app.use(bodyParser.json());
// Receive HTTP POST requests
app.post("/my-linear-webhook", (req, res) => {
const payload = req.body;
const { action, data, type, createdAt } = payload;
// Verify signature
const signature = crypto.createHmac("sha256", WEBHOOK_SECRET).update(rawBody).digest("hex");
if (signature !== request.headers['linear-signature']) {
res.sendStatus(400);
return
}
// Do something neat with the data received!
// Finally, respond with a HTTP 200 to signal all good
res.sendStatus(200);
});
app.listen(port, () => console.log(`My webhook consumer listening on port ${port}!`));
The easiest way to configure a Webhook is via API Settings. Open Settings and find "API".

Webhooks configuration in API Settings UI
Click on "New webhook", and specify the URL in which you have an endpoint ready to receive HTTP POST requests. Label is used to identify webhooks and describe their purpose.

Your newly created webhook will be listed and is ready to be used. Your defined URL of
http://example.com/webhooks/linear-updates
will now get notified of any updates for your chosen models.Once you've created an API token and found out the
teamId
that will own the Webhook (if it is not going to be for all teams on that organization), you're ready to get going.Creating a new Webhook
To create a new Webhook via the API, you can create a new Webhook with by calling a
webhookCreate
mutation with the teamId
(or allPublicTeams: true
) and url
of your webhook, and the preferred resourceTypes
([Comment, Issue, IssueLabel, Project, Cycle, Reaction]
):mutation {
webhookCreate(
input: {
url: "http://example.com/webhooks/linear-consumer"
teamId: "72b2a2dc-6f4f-4423-9d34-24b5bd10634a"
resourceTypes: ["Issue"]
}
) {
success
webhook {
id
enabled
}
}
}
The server should respond with a
success
flag, along the id
of your newly created webhook:{
"data": {
"webhookCreate": {
"success": true,
"webhook": {
"id": "790ce3f6-ea44-473d-bbd9-f3c73dc745a9",
"enabled": true
}
}
}
}
That's it! Your webhook is now ready to use and enabled by default. You can try it out e.g. by commenting on an Issue on your team, or maybe creating a new Issue.
Querying existing webhooks
Your webhooks belong to an Organization. You can either query all webhooks in your organization, or find them via their respective teams.
Querying all webhooks in your organization (the results are paginated, so you will need to include the
nodes
property.):query {
webhooks {
nodes {
id
url
enabled
team {
id
name
}
}
}
}
Querying webhooks via their associated teams:
query {
teams {
nodes {
webhooks {
nodes {
id
url
enabled
creator {
name
}
}
}
}
}
}
Deleting a webhook
Deleting a webhook is done with the
webhookDelete
mutation, by supplying the id
of the webhook in question:mutation {
webhookDelete(
id: "1087f03a-180a-4c31-b7dc-03dbe761ff59"
) {
success
}
}
The webhook HTTP payload will include information both in its HTTP headers and its request body.
The format of the payload body reflects that of the corresponding GraphQL entity. To get a hang of the data contained in the various objects, feel free to play around with GraphQL queries against Linear's API.
The payload will be sent with the following HTTP headers:
Accept-Charset: utf-8
Content-Type: application/json; charset=utf-8
Linear-Delivery: 234d1a4e-b617-4388-90fe-adc3633d6b72
Linear-Event: Issue
Linear-Signature: 766e1d90a96e2f5ecec342a99c5552999dd95d49250171b902d703fd674f5086
User-Agent: Linear-Webhook
Where the custom headers include:
Name | Description |
Linear-Delivery | An UUID (v4) uniquely identifying this payload. |
Linear-Event | The Entity type which triggered this event: Issue , Comment etc |
Linear-Signature | HMAC signature of the webhook payload |
These fields are present on all data change events.
Field | Description |
action | The type of the action that took place: create , update or remove . |
type | The type of entity that was targeted by the action. |
createdAt | The date and time that the action took place. |
data | The serialized value of the subject entity. |
url | URL where you can open up the subject entity. |
updatedFrom | For update actions, an object containing the previous values of all updated properties. |
webhookTimestamp | UNIX timestamp when the webhook was sent. |
webhookId | ID uniquely identifying this webhook. |
For example:
{
"action": "create",
"data": {
"id": "2174add1-f7c8-44e3-bbf3-2d60b5ea8bc9",
"createdAt": "2020-01-23T12:53:18.084Z",
"updatedAt": "2020-01-23T12:53:18.084Z",
"archivedAt": null,
"body": "Indeed, I think this is definitely an improvement over the previous version.",
"edited": false,
"issueId": "539068e2-ae88-4d09-bd75-22eb4a59612f",
"userId": "aacdca22-6266-4c0a-ab3c-8fa70a26765c"
},
"type": "Comment",
"url": "https://linear.app/issue/LIN-1778/foo-bar#comment-77217de3-fb52-4dad-bb9a-b356beb93de8",
"createdAt": "2020-01-23T12:53:18.084Z",
"webhookTimestamp": 1676056940508,
"webhookId": "000042e3-d123-4980-b49f-8e140eef9329"
}
These fields will be present on all other events as well.
Field | Description |
action | The type of the action that took place. Specific to the event stream. For Issue SLA this is for example one of set , highRisk and breached . |
type | The type of entity that was targeted by the action. |
createdAt | The date and time that the action took place. |
url | URL where you can open up the subject entity. |
updatedFrom | For update actions, an object containing the previous values of all updated properties. Properties that were previously not set, will have a value of null . |
webhookTimestamp | UNIX timestamp when the webhook was sent. |
webhookId | ID uniquely identifying this webhook. |
These fields are present on Issue SLA events.
Field | Description |
issueData | The serialized value of the issue. |
We support securing webhooks through content hashing with a signature. SHA256 HMAC signature is calculated of the content and delivered in
Linear-Signature
header which can used for comparison. Content body also includes a field webhookTimestamp
with UNIX timestamp of the time when webhook was sent. It's recommended you verify that it's within a minute of the time your system sees it to prevent replay attacks.To verify the webhook, calculate the signature from request body using the webhook secret available in developer settings. It's recommended to use raw request body content for the hashing as using JSON parsing might change it.
const signature = crypto.createHmac("sha256", WEBHOOK_SECRET).update(rawBody).digest("hex");
if (signature !== request.headers['linear-signature']) {
throw "Invalid signature"
}
In addition to verifying webhook, it's recommended to validate the sender IP addresses. See section above for the list.
Last modified 2mo ago