Production-grade Linear integration architecture patterns.
Use when designing system architecture, planning integrations,
or reviewing architectural decisions.
Trigger with phrases like "linear architecture", "linear system design",
"linear integration patterns", "linear best practices architecture".
Webhook-centric architecture. Minimal API calls, real-time processing.
// src/event-processor.ts
import express from "express";
import crypto from "crypto";
import { EventEmitter } from "events";
// Internal event bus
const bus = new EventEmitter();
// Webhook ingester
const app = express();
app.post("/webhooks/linear", express.raw({ type: "*/*" }), (req, res) => {
const sig = req.headers["linear-signature"] as string;
const body = req.body.toString();
const expected = crypto.createHmac("sha256", process.env.LINEAR_WEBHOOK_SECRET!)
.update(body).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(401).end();
}
const event = JSON.parse(body);
res.json({ ok: true });
// Emit to internal consumers
bus.emit(`${event.type}.${event.action}`, event);
bus.emit(event.type, event);
bus.emit("*", event);
});
// Consumer: Slack notifications
bus.on("Issue.update", async (event) => {
if (event.updatedFrom?.stateId && event.data.state?.type === "completed") {
await notifySlack(`Done: ${event.data.identifier} ${event.data.title}`);
}
});
// Consumer: Database sync
bus.on("Issue", async (event) => {
if (event.action === "create") await db.issues.insert(event.data);
if (event.action === "update") await db.issues.update(event.data.id, event.data);
if (event.action === "remove") await db.issues.softDelete(event.data.id);
});
// Consumer: Cache invalidation
bus.on("*", (event) => {
gateway.invalidate(event.type.toLowerCase());
});
Architecture 4: CQRS with Local State
Separate read and write paths. Full local state for complex queries, API for writes.
// Write side: mutations go through Linear API
async function createIssue(input: any) {
const result = await gateway.createIssue(input);
// Local state updated via webhook, not here
return result;
}
// Read side: queries against local database (no API calls)
async function getSprintVelocity(teamKey: string, sprints: number) {
return db.query(`
SELECT c.name, SUM(i.estimate) as velocity
FROM cycles c
JOIN issues i ON i.cycle_id = c.id AND i.state_type = 'completed'
WHERE c.team_key = ? AND c.completed_at IS NOT NULL
ORDER BY c.completed_at DESC
LIMIT ?
`, [teamKey, sprints]);
}
// Sync: webhook events keep local state fresh
// Full sync: daily consistency check (see linear-data-handling)
Project Structure
src/
linear/
gateway.ts # Rate-limited, cached API access
webhook-handler.ts # Signature verification + routing
event-bus.ts # Internal event distribution
cache.ts # TTL cache with invalidation
services/
issue-service.ts # Business logic
sync-service.ts # Data synchronization
config/
linear.ts # Environment config + validation