Все статьи
Безопасное тестирование webhook Linear на localhost
Linearwebhookslocalhostdeveloper integrations

Безопасное тестирование webhook Linear на localhost

Внешний сервис не может обратиться к localhost напрямую. Туннель предоставляет публичный HTTPS URL и передаёт локальному серверу настоящие headers и неизменённый body. Сохраните raw body до JSON parser. Сначала проверьте подпись и данные, надёжно запишите доставку и только затем запускайте бизнес-логику.

Как webhook Linear попадает в локальное приложение

Внешний сервис не может обратиться к localhost напрямую. Туннель предоставляет публичный HTTPS URL и передаёт локальному серверу настоящие headers и неизменённый body. >официальная документация

1. Создайте endpoint с сохранением raw body

Сохраните raw body до JSON parser. Сначала проверьте подпись и данные, надёжно запишите доставку и только затем запускайте бизнес-логику.

import express from "express";
import crypto from "node:crypto";

const app = express();
const secret = process.env.LINEAR_WEBHOOK_SECRET;

app.post(
  "/webhooks/linear",
  express.raw({ type: "application/json", limit: "1mb" }),
  async (req, res) => {
    const rawBody = req.body;
    const signature = req.get("linear-signature");

    if (!verifyLinearSignature(signature, rawBody, secret)) {
      return res.sendStatus(401);
    }

    let payload;
    try {
      payload = JSON.parse(rawBody.toString("utf8"));
    } catch {
      return res.sendStatus(400);
    }

    if (!Number.isFinite(payload.webhookTimestamp) ||
        Math.abs(Date.now() - payload.webhookTimestamp) > 60_000) {
      return res.sendStatus(401);
    }

    const deliveryId = req.get("linear-delivery") || payload.webhookId;
    if (!deliveryId) return res.sendStatus(400);

    try {
      await recordAndEnqueueOnce(deliveryId, payload);
      return res.sendStatus(200);
    } catch (error) {
      console.error("Linear webhook persistence failed", error);
      return res.sendStatus(500);
    }
  }
);

app.use(express.json());
app.listen(3000);

Сохраните raw body до JSON parser. Сначала проверьте подпись и данные, надёжно запишите доставку и только затем запускайте бизнес-логику. >официальная документация

2. Откройте локальный порт через HTTPS

Запустите сервер и держите туннель открытым во втором терминале. Ответ 401 на запрос без подписи подтверждает работу routing и проверки доступа.

npx portpreview 3000
https://example.portpreview.dev/webhooks/linear

Запустите сервер и держите туннель открытым во втором терминале. Ответ 401 на запрос без подписи подтверждает работу routing и проверки доступа.

3. Настройте webhook в Linear

Укажите полный HTTPS URL, подпишитесь только на нужные события и вызовите реальный сценарий в тестовой среде. Храните secret вне репозитория.

Укажите полный HTTPS URL, подпишитесь только на нужные события и вызовите реальный сценарий в тестовой среде. Храните secret вне репозитория.

Проверьте подпись по точным байтам

Вычислите HMAC с webhook secret и точными данными из спецификации. Сравнивайте за постоянное время и не записывайте secret в логи.

function verifyLinearSignature(signature, rawBody, secret) {
  if (!secret || typeof signature !== "string" ||
      !/^[0-9a-f]{64}$/i.test(signature)) {
    return false;
  }

  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest();
  const actual = Buffer.from(signature, "hex");

  return actual.length === expected.length &&
    crypto.timingSafeEqual(actual, expected);
}

Вычислите HMAC с webhook secret и точными данными из спецификации. Сравнивайте за постоянное время и не записывайте secret в логи. >практическое руководство

Защититесь от replay с помощью timestamp

После проверки подписи контролируйте свежесть timestamp и синхронизируйте часы сервера.

После проверки подписи контролируйте свежесть timestamp и синхронизируйте часы сервера.

Разберитесь с payload и ID доставки

Обрабатывайте известные type, допускайте новые поля и дедуплицируйте по стабильному ID доставки в постоянном хранилище.

Обрабатывайте известные type, допускайте новые поля и дедуплицируйте по стабильному ID доставки в постоянном хранилище.

Отвечайте быстро и обрабатывайте идемпотентно

Резервируйте ID доставки и создавайте job в одной transaction до ответа 200. При недоступном хранилище верните ошибку для retry.

Резервируйте ID доставки и создавайте job в одной transaction до ответа 200. При недоступном хранилище верните ошибку для retry. >практическое руководство

Протестируйте весь локальный поток

  1. Запустите сервер и держите туннель открытым во втором терминале. Ответ 401 на запрос без подписи подтверждает работу routing и проверки доступа.
  2. Укажите полный HTTPS URL, подпишитесь только на нужные события и вызовите реальный сценарий в тестовой среде. Храните secret вне репозитория.
  3. Вычислите HMAC с webhook secret и точными данными из спецификации. Сравнивайте за постоянное время и не записывайте secret в логи.
  4. Резервируйте ID доставки и создавайте job в одной transaction до ответа 200. При недоступном хранилище верните ошибку для retry.

Проверьте routing, headers, подпись, время ответа и идемпотентность. Синтетические запросы полезнее всего для сценариев отказа.

Диагностика сбоев webhook Linear

  • Запустите сервер и держите туннель открытым во втором терминале. Ответ 401 на запрос без подписи подтверждает работу routing и проверки доступа.
  • Вычислите HMAC с webhook secret и точными данными из спецификации. Сравнивайте за постоянное время и не записывайте secret в логи.
  • После проверки подписи контролируйте свежесть timestamp и синхронизируйте часы сервера.
  • Проверьте POST path, port туннеля, raw body, secret, часы и задержку базы данных.

>практическое руководство

Проверка безопасности для разработки и production

  • Требуйте HTTPS, ограничьте размер и method, защищайте secrets, очищайте логи и удаляйте устаревшие тестовые URL.
  • Сохраните raw body до JSON parser. Сначала проверьте подпись и данные, надёжно запишите доставку и только затем запускайте бизнес-логику.
  • Резервируйте ID доставки и создавайте job в одной transaction до ответа 200. При недоступном хранилище верните ошибку для retry.

Требуйте HTTPS, ограничьте размер и method, защищайте secrets, очищайте логи и удаляйте устаревшие тестовые URL. >практическое руководство

Часто задаваемые вопросы

Как протестировать webhook Linear на localhost?
Запустите сервер и держите туннель открытым во втором терминале. Ответ 401 на запрос без подписи подтверждает работу routing и проверки доступа.
Как проверить подпись webhook Linear?
Вычислите HMAC с webhook secret и точными данными из спецификации. Сравнивайте за постоянное время и не записывайте secret в логи.
Почему Linear повторяет доставку webhook?
Резервируйте ID доставки и создавайте job в одной transaction до ответа 200. При недоступном хранилище верните ошибку для retry.
Какой ключ использовать для идемпотентности?
Обрабатывайте известные type, допускайте новые поля и дедуплицируйте по стабильному ID доставки в постоянном хранилище.