Todos os artigos
Como testar webhooks do Linear no localhost com segurança
Linearwebhookslocalhostdeveloper integrations

Como testar webhooks do Linear no localhost com segurança

Um serviço remoto não consegue acessar localhost diretamente. O túnel oferece uma URL HTTPS pública e encaminha os headers reais e o body original ao servidor local. Preserve o raw body antes do JSON parser. Valide assinatura e dados, grave a entrega de forma durável e só então execute a lógica de negócio.

Como o webhook do Linear chega a uma aplicação local

Um serviço remoto não consegue acessar localhost diretamente. O túnel oferece uma URL HTTPS pública e encaminha os headers reais e o body original ao servidor local. >documentação oficial

1. Crie um endpoint que preserve o raw body

Preserve o raw body antes do JSON parser. Valide assinatura e dados, grave a entrega de forma durável e só então execute a lógica de negócio.

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);

Preserve o raw body antes do JSON parser. Valide assinatura e dados, grave a entrega de forma durável e só então execute a lógica de negócio. >documentação oficial

2. Exponha a porta local por HTTPS

Inicie o servidor e mantenha o túnel aberto em outro terminal. Um 401 para uma requisição sem assinatura confirma que o routing funciona e a autenticação rejeita corretamente.

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

Inicie o servidor e mantenha o túnel aberto em outro terminal. Um 401 para uma requisição sem assinatura confirma que o routing funciona e a autenticação rejeita corretamente.

3. Configure o webhook no Linear

Cadastre a URL HTTPS completa, assine apenas os eventos necessários e gere um caso real no ambiente de teste. Guarde o secret fora do repositório.

Cadastre a URL HTTPS completa, assine apenas os eventos necessários e gere um caso real no ambiente de teste. Guarde o secret fora do repositório.

Valide a assinatura sobre os bytes exatos

Calcule o HMAC com o webhook secret e os dados exatos definidos pelo provedor. Compare em tempo constante e nunca registre o 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);
}

Calcule o HMAC com o webhook secret e os dados exatos definidos pelo provedor. Compare em tempo constante e nunca registre o secret. >guia prático

Evite ataques de replay com o timestamp

Confira a validade do timestamp depois da assinatura e mantenha o relógio do host sincronizado.

Confira a validade do timestamp depois da assinatura e mantenha o relógio do host sincronizado.

Entenda o payload e os identificadores de entrega

Processe apenas type conhecidos, aceite campos adicionais e elimine duplicidades com um ID de entrega estável em armazenamento durável.

Processe apenas type conhecidos, aceite campos adicionais e elimine duplicidades com um ID de entrega estável em armazenamento durável.

Responda rápido e processe com idempotência

Reserve o ID e crie o job na mesma transaction antes de responder 200. Se a persistência falhar, retorne erro para permitir retry.

Reserve o ID e crie o job na mesma transaction antes de responder 200. Se a persistência falhar, retorne erro para permitir retry. >guia prático

Teste o fluxo local completo

  1. Inicie o servidor e mantenha o túnel aberto em outro terminal. Um 401 para uma requisição sem assinatura confirma que o routing funciona e a autenticação rejeita corretamente.
  2. Cadastre a URL HTTPS completa, assine apenas os eventos necessários e gere um caso real no ambiente de teste. Guarde o secret fora do repositório.
  3. Calcule o HMAC com o webhook secret e os dados exatos definidos pelo provedor. Compare em tempo constante e nunca registre o secret.
  4. Reserve o ID e crie o job na mesma transaction antes de responder 200. Se a persistência falhar, retorne erro para permitir retry.

Confira routing, headers, assinatura, tempo de resposta e idempotência. Use requisições sintéticas principalmente para testar rejeições.

Resolva falhas do webhook do Linear

  • Inicie o servidor e mantenha o túnel aberto em outro terminal. Um 401 para uma requisição sem assinatura confirma que o routing funciona e a autenticação rejeita corretamente.
  • Calcule o HMAC com o webhook secret e os dados exatos definidos pelo provedor. Compare em tempo constante e nunca registre o secret.
  • Confira a validade do timestamp depois da assinatura e mantenha o relógio do host sincronizado.
  • Revise o POST path, a port do túnel, o raw body, o secret, o relógio e a latência do banco de dados.

>guia prático

Checklist de segurança para desenvolvimento e produção

  • Exija HTTPS, limite tamanho e method, proteja secrets, remova dados sensíveis dos logs e exclua URLs de teste antigas.
  • Preserve o raw body antes do JSON parser. Valide assinatura e dados, grave a entrega de forma durável e só então execute a lógica de negócio.
  • Reserve o ID e crie o job na mesma transaction antes de responder 200. Se a persistência falhar, retorne erro para permitir retry.

Exija HTTPS, limite tamanho e method, proteja secrets, remova dados sensíveis dos logs e exclua URLs de teste antigas. >guia prático

Perguntas frequentes

Como testar um webhook do Linear no localhost?
Inicie o servidor e mantenha o túnel aberto em outro terminal. Um 401 para uma requisição sem assinatura confirma que o routing funciona e a autenticação rejeita corretamente.
Como validar a assinatura do webhook do Linear?
Calcule o HMAC com o webhook secret e os dados exatos definidos pelo provedor. Compare em tempo constante e nunca registre o secret.
Por que o Linear tenta entregar o webhook novamente?
Reserve o ID e crie o job na mesma transaction antes de responder 200. Se a persistência falhar, retorne erro para permitir retry.
Qual chave usar para idempotência?
Processe apenas type conhecidos, aceite campos adicionais e elimine duplicidades com um ID de entrega estável em armazenamento durável.