Внешний сервис не может обратиться к 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. >практическое руководство
Протестируйте весь локальный поток
- Запустите сервер и держите туннель открытым во втором терминале. Ответ 401 на запрос без подписи подтверждает работу routing и проверки доступа.
- Укажите полный HTTPS URL, подпишитесь только на нужные события и вызовите реальный сценарий в тестовой среде. Храните secret вне репозитория.
- Вычислите HMAC с webhook secret и точными данными из спецификации. Сравнивайте за постоянное время и не записывайте secret в логи.
- Резервируйте 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. >практическое руководство
