Todos los artículos
Probar webhooks de Linear en localhost de forma segura
Linearwebhookslocalhostdeveloper integrations

Probar webhooks de Linear en localhost de forma segura

Un servicio remoto no puede acceder a localhost. El túnel ofrece una URL HTTPS pública y reenvía al servidor local los headers reales y el body original. Conserva el body sin modificar antes del parser JSON. Valida firma y datos, registra la entrega de forma duradera y después ejecuta la lógica de negocio.

Cómo llega un webhook de Linear a una aplicación local

Un servicio remoto no puede acceder a localhost. El túnel ofrece una URL HTTPS pública y reenvía al servidor local los headers reales y el body original. >documentación oficial

1. Crear un endpoint que conserve el body sin modificar

Conserva el body sin modificar antes del parser JSON. Valida firma y datos, registra la entrega de forma duradera y después ejecuta la lógica de negocio.

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

Conserva el body sin modificar antes del parser JSON. Valida firma y datos, registra la entrega de forma duradera y después ejecuta la lógica de negocio. >documentación oficial

2. Exponer el puerto local mediante HTTPS

Inicia el servidor y mantén el túnel abierto en otra terminal. Un 401 para una solicitud sin firma confirma que el routing funciona y la autenticación la rechaza.

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

Inicia el servidor y mantén el túnel abierto en otra terminal. Un 401 para una solicitud sin firma confirma que el routing funciona y la autenticación la rechaza.

3. Configurar el webhook en Linear

Registra la URL HTTPS completa, suscribe solo los eventos necesarios y provoca un caso real en un entorno de prueba. Guarda el secret fuera del repositorio.

Registra la URL HTTPS completa, suscribe solo los eventos necesarios y provoca un caso real en un entorno de prueba. Guarda el secret fuera del repositorio.

Validar la firma sobre los bytes exactos

Calcula el HMAC con el secret del webhook y los datos exactos definidos por el proveedor. Compara en tiempo constante y nunca registres el 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);
}

Calcula el HMAC con el secret del webhook y los datos exactos definidos por el proveedor. Compara en tiempo constante y nunca registres el secret. >guía práctica

Evitar ataques de replay con el timestamp

Comprueba la vigencia del timestamp después de validar la firma y mantén sincronizado el reloj del host.

Comprueba la vigencia del timestamp después de validar la firma y mantén sincronizado el reloj del host.

Entender el payload y los identificadores de entrega

Procesa solo los type conocidos, tolera campos adicionales y deduplica con un identificador de entrega estable en almacenamiento duradero.

Procesa solo los type conocidos, tolera campos adicionales y deduplica con un identificador de entrega estable en almacenamiento duradero.

Responder rápido y procesar de forma idempotente

Reserva el identificador y crea el job en una misma transacción antes de responder 200. Si falla la persistencia, devuelve un error para permitir el reintento.

Reserva el identificador y crea el job en una misma transacción antes de responder 200. Si falla la persistencia, devuelve un error para permitir el reintento. >guía práctica

Probar el flujo local completo

  1. Inicia el servidor y mantén el túnel abierto en otra terminal. Un 401 para una solicitud sin firma confirma que el routing funciona y la autenticación la rechaza.
  2. Registra la URL HTTPS completa, suscribe solo los eventos necesarios y provoca un caso real en un entorno de prueba. Guarda el secret fuera del repositorio.
  3. Calcula el HMAC con el secret del webhook y los datos exactos definidos por el proveedor. Compara en tiempo constante y nunca registres el secret.
  4. Reserva el identificador y crea el job en una misma transacción antes de responder 200. Si falla la persistencia, devuelve un error para permitir el reintento.

Comprueba routing, headers, firma, tiempo de respuesta e idempotencia. Usa solicitudes sintéticas principalmente para probar rechazos.

Solucionar fallos del webhook de Linear

  • Inicia el servidor y mantén el túnel abierto en otra terminal. Un 401 para una solicitud sin firma confirma que el routing funciona y la autenticación la rechaza.
  • Calcula el HMAC con el secret del webhook y los datos exactos definidos por el proveedor. Compara en tiempo constante y nunca registres el secret.
  • Comprueba la vigencia del timestamp después de validar la firma y mantén sincronizado el reloj del host.
  • Revisa el path POST, el port del túnel, el body original, el secret, el reloj y la latencia de la base de datos.

>guía práctica

Lista de seguridad para desarrollo y producción

  • Exige HTTPS, limita tamaño y method, protege secrets, anonimiza logs y elimina URLs de prueba antiguas.
  • Conserva el body sin modificar antes del parser JSON. Valida firma y datos, registra la entrega de forma duradera y después ejecuta la lógica de negocio.
  • Reserva el identificador y crea el job en una misma transacción antes de responder 200. Si falla la persistencia, devuelve un error para permitir el reintento.

Exige HTTPS, limita tamaño y method, protege secrets, anonimiza logs y elimina URLs de prueba antiguas. >guía práctica

Preguntas frecuentes

¿Cómo pruebo un webhook de Linear en localhost?
Inicia el servidor y mantén el túnel abierto en otra terminal. Un 401 para una solicitud sin firma confirma que el routing funciona y la autenticación la rechaza.
¿Cómo valido la firma del webhook de Linear?
Calcula el HMAC con el secret del webhook y los datos exactos definidos por el proveedor. Compara en tiempo constante y nunca registres el secret.
¿Por qué Linear reintenta el webhook?
Reserva el identificador y crea el job en una misma transacción antes de responder 200. Si falla la persistencia, devuelve un error para permitir el reintento.
¿Qué clave debo usar para la idempotencia?
Procesa solo los type conocidos, tolera campos adicionales y deduplica con un identificador de entrega estable en almacenamiento duradero.