linear-webhooks-events
Configure and handle Linear webhooks for real-time event processing. Use when setting up webhooks, handling Linear events, or building real-time integrations. Trigger with phrases like "linear webhooks", "linear events", "linear real-time", "handle linear webhook", "linear webhook setup".
Install
mkdir -p .claude/skills/linear-webhooks-events && curl -L -o skill.zip "https://mcp.directory/api/skills/download/2391" && unzip -o skill.zip -d .claude/skills/linear-webhooks-events && rm skill.zipInstalls to .claude/skills/linear-webhooks-events
About this skill
Linear Webhooks & Events
Overview
Set up and handle Linear webhooks for real-time event processing. Linear sends webhook payloads for issues, comments, projects, cycles, and other entities with HMAC-SHA256 signature verification.
Prerequisites
- Linear workspace admin access
- Public HTTPS endpoint for webhook delivery
- Webhook signing secret (generated during webhook creation)
Instructions
Step 1: Create Webhook Endpoint
Build a webhook receiver with signature verification.
import express from "express";
import crypto from "crypto";
const app = express();
// IMPORTANT: parse raw body for signature verification
app.post("/webhooks/linear", express.raw({ type: "*/*" }), (req, res) => {
const signature = req.headers["linear-signature"] as string;
const delivery = req.headers["linear-delivery"] as string;
const eventType = req.headers["linear-event"] as string;
const body = req.body.toString();
// Verify HMAC-SHA256 signature
const expected = crypto
.createHmac("sha256", process.env.LINEAR_WEBHOOK_SECRET!)
.update(body)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
console.error(`Invalid signature for delivery ${delivery}`);
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(body);
console.log(`Received ${eventType} ${event.action} (delivery: ${delivery})`);
// Process asynchronously, respond quickly
processEvent(event).catch(err => console.error("Processing failed:", err));
res.status(200).json({ received: true });
});
app.listen(3000);
Step 2: Event Processing Router
Route events by entity type and action.
interface LinearWebhookPayload {
action: "create" | "update" | "remove";
type: string; // "Issue", "Comment", "Project", "Cycle", etc.
data: Record<string, any>;
url: string;
actor?: { id: string; type: string; name?: string };
updatedFrom?: Record<string, any>; // Previous values on update
createdAt: string;
webhookTimestamp: number;
}
type EventHandler = (event: LinearWebhookPayload) => Promise<void>;
const handlers: Record<string, Record<string, EventHandler>> = {
Issue: {
create: async (e) => {
console.log(`New issue: ${e.data.identifier} — ${e.data.title}`);
await notifySlack(`New issue: [${e.data.identifier}](${e.url}) ${e.data.title}`);
},
update: async (e) => {
// Check what changed using updatedFrom
if (e.updatedFrom?.stateId) {
console.log(`${e.data.identifier} state changed to ${e.data.state?.name}`);
if (e.data.state?.type === "completed") {
await notifySlack(`Done: [${e.data.identifier}](${e.url}) ${e.data.title}`);
}
}
if (e.updatedFrom?.assigneeId) {
console.log(`${e.data.identifier} assigned to ${e.data.assignee?.name}`);
}
},
remove: async (e) => {
console.log(`Issue deleted: ${e.data.identifier}`);
},
},
Comment: {
create: async (e) => {
console.log(`New comment on ${e.data.issue?.identifier}: ${e.data.body?.substring(0, 80)}`);
},
},
Project: {
update: async (e) => {
if (e.updatedFrom?.state) {
console.log(`Project "${e.data.name}" status: ${e.data.state}`);
}
},
},
};
async function processEvent(event: LinearWebhookPayload): Promise<void> {
const handler = handlers[event.type]?.[event.action];
if (handler) {
await handler(event);
} else {
console.log(`Unhandled: ${event.type}.${event.action}`);
}
}
Step 3: Idempotent Event Processing
Linear may retry webhook deliveries. Use the Linear-Delivery header as a deduplication key.
const processedDeliveries = new Set<string>();
function isNewDelivery(deliveryId: string): boolean {
if (processedDeliveries.has(deliveryId)) return false;
processedDeliveries.add(deliveryId);
// Clean up old entries periodically
if (processedDeliveries.size > 10000) {
const entries = [...processedDeliveries];
entries.slice(0, 5000).forEach(id => processedDeliveries.delete(id));
}
return true;
}
// In webhook handler:
app.post("/webhooks/linear", express.raw({ type: "*/*" }), (req, res) => {
const delivery = req.headers["linear-delivery"] as string;
// ... signature verification ...
if (!isNewDelivery(delivery)) {
return res.status(200).json({ status: "duplicate, skipped" });
}
// Process event...
});
Step 4: Register Webhook in Linear
# Via Linear UI: Settings > API > Webhooks > New webhook
# URL: https://your-app.com/webhooks/linear
# Select resource types: Issues, Comments, Projects
# Or via API:
curl -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation { webhookCreate(input: { url: \"https://your-app.com/webhooks/linear\", resourceTypes: [\"Issue\", \"Comment\", \"Project\"], allPublicTeams: true }) { success webhook { id enabled } } }"
}'
Step 5: Local Development with ngrok
# Start your webhook server
npm run dev # listening on port 3000
# In another terminal, expose via ngrok
ngrok http 3000
# Copy the https://*.ngrok.io URL
# Create a webhook in Linear with the ngrok URL
# Settings > API > Webhooks > New webhook
# URL: https://abc123.ngrok.io/webhooks/linear
Error Handling
| Error | Cause | Solution |
|---|---|---|
Invalid signature (401) | Wrong webhook secret or body parsing | Use express.raw(), verify secret matches Linear settings |
| Webhook not received | URL not publicly accessible | Check HTTPS, firewall, ngrok tunnel |
| Duplicate processing | Linear retried delivery | Deduplicate using Linear-Delivery header |
Timeout on delivery | Handler takes too long | Respond 200 immediately, process async |
Missing updatedFrom | Field didn't change | updatedFrom only contains changed fields |
Examples
Slack Notification on Issue State Change
async function notifySlack(message: string) {
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: message }),
});
}
// In Issue.update handler:
if (e.updatedFrom?.stateId && e.data.state?.type === "completed") {
await notifySlack(
`:white_check_mark: *${e.data.identifier}* completed by ${e.actor?.name}\n${e.data.title}`
);
}
List Active Webhooks
const client = getLinearClient();
const webhooks = await client.webhooks();
for (const wh of webhooks.nodes) {
console.log(`${wh.url} — enabled: ${wh.enabled}, types: ${wh.resourceTypes}`);
}
Output
- Express webhook endpoint with HMAC-SHA256 signature verification
- Event router dispatching by entity type and action
- Idempotent delivery processing using deduplication set
- Webhook registration via API and UI
- ngrok setup for local development testing
Resources
Next Steps
Optimize performance with linear-performance-tuning.
More by jeremylongshore
View all skills by jeremylongshore →You might also like
flutter-development
aj-geddes
Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.
drawio-diagrams-enhanced
jgtolentino
Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.
godot
bfollington
This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.
ui-ux-pro-max
nextlevelbuilder
"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."
nano-banana-pro
garg-aayush
Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.
fastapi-templates
wshobson
Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.
Related MCP Servers
Browse all serversEffortlessly manage Netlify projects with AI using the Netlify MCP Server—automate deployment, sites, and more via natur
Integrate with Superjolt to manage JavaScript apps, configure domains, set environment variables, and monitor logs using
Unlock AI-ready web data with Firecrawl: scrape any website, handle dynamic content, and automate web scraping for resea
Create modern React UI components instantly with Magic AI Agent. Integrates with top IDEs for fast, stunning design and
MCP Installer simplifies dynamic installation and configuration of additional MCP servers. Get started easily with MCP I
Powerful MCP server for Slack with advanced API, message fetching, webhooks, and enterprise features. Robust Slack data
Stay ahead of the MCP ecosystem
Get weekly updates on new skills and servers.