Linear Webhook: Example Payload, Headers and Signature

A Linear webhook reports a change to one entity — an issue, a comment, a project — and, on updates, tells you the previous values of whatever changed. Here is an issue moving between states, with the headers and the signature.

No webhook URL yet. Creating one needs no account.

Request details
Requests sent to your webhook URL appear here: method, headers and the body exactly as it arrived.

The button posts the example below to your webhook URL from your browser. It is not sent by Linear, it carries your browser's User-Agent, and its signature is a placeholder that will not verify. To see a real delivery, follow the steps further down.

Last updated . How this page is checked.

How the webhook URL works. It is a public address that records whatever is sent to it and answers 200. An inbox keeps the 50 most recent requests, accepts bodies up to 256 KB, and is deleted 24 hours after it was created — or sooner if you press Delete URL or our server restarts, because requests are held in memory and never written to disk.

Anyone who has the URL can read what it received. Use sandbox and test-mode accounts, do not route production traffic or customer data through it, and delete it when you are done. Privacy details.

What is a Linear webhook?

A Linear webhook is an HTTP POST sent to a URL registered in a workspace when data of a subscribed type changes: issues, comments, projects, cycles, labels, reactions and more. A webhook covers one team or all public teams, and you choose the resource types it receives when you create it.

Every payload has the same frame. type is the kind of entity, action is create, update or remove, and data is the entity after the change. On an update, updatedFrom lists the fields that changed with the values they had before, which turns "an issue changed" into "the state moved from this to that" without a second request.

What a Linear webhook request looks like

The request line and headers as they reach your server, followed by the body. Ids, tokens and the signature are placeholders.

POST /your/webhook/path HTTP/1.1
Host: your-server.example
Linear-Signature: 766e1d90a96e2f5ecec342a99c5552999dd95d49250171b902d703fd674f5086
Linear-Event: Issue
Linear-Delivery: 234d1a4e-b617-4388-90fe-adc3633d6b72
Content-Type: application/json; charset=utf-8
User-Agent: Linear-Webhook
{
  "action": "update",
  "type": "Issue",
  "createdAt": "2026-01-01T12:00:00.000Z",
  "data": {
    "id": "2174add1-f7c8-44e3-bbf3-2d60b5ea8bc9",
    "identifier": "ENG-417",
    "number": 417,
    "title": "Webhook worker drops retries",
    "priority": 2,
    "teamId": "72b2a2dc-6f4f-4423-9d34-24b5bd10634a",
    "stateId": "a1c3e5f7-0b2d-4f6a-8c9e-1d3f5a7b9c0e",
    "state": {
      "id": "a1c3e5f7-0b2d-4f6a-8c9e-1d3f5a7b9c0e",
      "name": "In Progress",
      "type": "started"
    },
    "team": {
      "id": "72b2a2dc-6f4f-4423-9d34-24b5bd10634a",
      "key": "ENG",
      "name": "Engineering"
    },
    "assigneeId": "c4f2a9d0-e3b1-4c8f-a6d5-e4b3c2a1f0e9",
    "labelIds": [],
    "createdAt": "2025-12-30T09:14:02.000Z",
    "updatedAt": "2026-01-01T12:00:00.000Z"
  },
  "updatedFrom": {
    "stateId": "0e9d8c7b-6a5f-4e3d-2c1b-0a9f8e7d6c5b",
    "updatedAt": "2025-12-31T16:40:11.000Z"
  },
  "url": "https://linear.app/acme/issue/ENG-417/webhook-worker-drops-retries",
  "organizationId": "9b8a7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
  "webhookId": "000042e3-d123-4980-b49f-8e140eef9329",
  "webhookTimestamp": 1767225600000
}

updatedFrom contains only the fields that changed, holding their old values; the new values are in data. Here the state id changed, so the issue was moved to "In Progress" from whatever state the old id names. webhookTimestamp is the send time in milliseconds and is there for replay protection. Priority is a number where 0 means none and 1 is the most urgent.

Headers Linear sends

HeaderExampleWhat it is
Linear-Signature766e1d90a96e2f5ecec342a99c5552999dd95d49250…HMAC-SHA256 of the body, hex, with no prefix.
Linear-EventIssueThe entity type. Matches type in the body.
Linear-Delivery234d1a4e-b617-4388-90fe-adc3633d6b72Unique id of this delivery.
Content-Typeapplication/json; charset=utf-8The body is JSON.
User-AgentLinear-WebhookIdentifies the sender.

Header names are case-insensitive. Your framework may show them lower-cased, and HTTP/2 always does.

How Linear signs a webhook

Each webhook has a signing secret, shown when you create it. Linear computes an HMAC-SHA256 of the raw request body with that secret and sends the hex digest, with no prefix, in Linear-Signature.

The timestamp is inside the body rather than in a header, so replay protection is a second step after the HMAC passes: parse the JSON and check that webhookTimestamp is within about a minute of your clock, as Linear's documentation recommends. Compute it over the raw request body exactly as received — before any JSON parsing — and compare with a constant-time function.

const crypto = require('crypto');

// rawBody: string of the body exactly as received
function verifyLinear(rawBody, header, secret, toleranceMs = 60000) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(header || '');
  const b = Buffer.from(expected);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return false;

  const sentAt = JSON.parse(rawBody).webhookTimestamp;
  return Math.abs(Date.now() - sentAt) <= toleranceMs;
}

Timeouts, retries and disabled webhooks

Linear expects an HTTP 200 within about five seconds, per its documentation at the time of writing. A slower or failed delivery is retried a small number of times with a delay that grows from minutes to hours. An endpoint that keeps failing can have its webhook disabled, at which point the workspace admins are told and someone has to re-enable it.

Changes made in quick succession produce separate deliveries that may not arrive in order. data.updatedAt tells you which snapshot is newer, and Linear-Delivery identifies a delivery if you need to recognise a repeat.

Catch a real Linear webhook

  1. Press Create webhook URL at the top of this page and copy the address.
  2. In Linear, open the workspace settings and find API → Webhooks. This needs admin rights.
  3. Create a webhook, give it a label, and paste the address as the URL.
  4. Tick the resource types to receive — Issues is enough to start — choose a team, and save. Note the signing secret.
  5. Change the status of an issue and read the delivery, including updatedFrom, in the inbox above.

To run a caught request against your own code, select it and press Open in API Tester: the method, headers and body are loaded into the request console, where you enter your server's public URL and send it. A body you edit will no longer match its signature — that is the signature doing its job.

Things that catch people out

  • The signature has no prefix. Unlike GitHub's sha256=…, the header is the bare hex digest.
  • remove is not always deletion. Archiving and trashing an entity surface as their own state changes; read data before deleting anything on your side.
  • Related entities arrive as ids. Some relations are expanded, as state and team are here, but most are only …Id fields to resolve through the GraphQL API.

api-tester.com is independent and is not affiliated with Linear. Providers change header names, limits and retry schedules; treat the official Linear documentation as the source of truth.

Related examples and tools

Other webhooks signed with an HMAC of the body

References

These links leave api-tester.com, which is independent of the companies and projects they point to.

Linear webhook FAQ

What does a Linear webhook payload look like?

A JSON object with action (create, update or remove), type (such as Issue or Comment), data containing the entity, updatedFrom with previous values on updates, plus url, organizationId, webhookId, createdAt and webhookTimestamp.

How do I verify a Linear webhook?

Compute an HMAC-SHA256 of the raw request body with the webhook signing secret, hex-encode it and compare it to the Linear-Signature header in constant time. Then check that webhookTimestamp in the body is recent.

What is updatedFrom in a Linear webhook?

An object present on update actions that lists the fields that changed, each with the value it had before the change. The current values are in data.

Does Linear retry failed webhooks?

Yes. A delivery that fails or takes longer than about five seconds is retried a few times with increasing delays, and a webhook that keeps failing can be disabled, according to Linear's documentation at the time of writing.

Which headers does Linear send with a webhook?

Linear-Signature with the HMAC, Linear-Event with the entity type, Linear-Delivery with a unique delivery id, a JSON Content-Type and a User-Agent of Linear-Webhook.