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
- 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.
- 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.
- 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.
- 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.
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
