Agent Skills by ALSEL
Anthropic Claudeソフトウェア開発⭐ リポ 1品質スコア 53/100

linear-webhooks-events

Linearのウェブフックを設定・管理して、リアルタイムイベント処理を実現します。ウェブフック設定時、Linearイベント処理、またはリアルタイム連携構築時に活用できます。「linearウェブフック」「linearイベント」「linearリアルタイム」「linearウェブフック処理」「linearウェブフック設定」といったフレーズで呼び出せます。

description の原文を見る

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

SKILL.md 本文

Linear Webhooks & Events

Overview

Linear webhooksをセットアップして、リアルタイムイベント通知を処理します。

Prerequisites

  • Linear workspace の管理者アクセス
  • Webhook配信用のパブリックエンドポイント
  • Webhook署名秘密鍵の設定

利用可能なイベントタイプ

イベントタイプ説明
IssueIssue の作成、更新、削除
IssueCommentコメントの追加または更新
Projectプロジェクトの変更
CycleCycle(スプリント)の変更
Labelラベルの変更
Reaction絵文字リアクション

手順

ステップ 1: Webhook エンドポイントの作成

// api/webhooks/linear.ts (Vercel/Next.js style)
import crypto from "crypto";
import type { NextApiRequest, NextApiResponse } from "next";

export const config = {
  api: {
    bodyParser: false, // Need raw body for signature
  },
};

async function getRawBody(req: NextApiRequest): Promise<string> {
  const chunks: Buffer[] = [];
  for await (const chunk of req) {
    chunks.push(chunk);
  }
  return Buffer.concat(chunks).toString("utf8");
}

function verifySignature(payload: string, signature: string): boolean {
  const secret = process.env.LINEAR_WEBHOOK_SECRET!;
  const hmac = crypto.createHmac("sha256", secret);
  const expectedSignature = hmac.update(payload).digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== "POST") {
    return res.status(405).json({ error: "Method not allowed" });
  }

  const rawBody = await getRawBody(req);
  const signature = req.headers["linear-signature"] as string;

  if (!signature || !verifySignature(rawBody, signature)) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const event = JSON.parse(rawBody);

  // Process event
  await processLinearEvent(event);

  return res.status(200).json({ received: true });
}

ステップ 2: イベント処理ルーター

// lib/webhook-handlers.ts
interface LinearWebhookPayload {
  action: "create" | "update" | "remove";
  type: string;
  data: Record<string, unknown>;
  createdAt: string;
  organizationId: string;
  webhookTimestamp: number;
  webhookId: string;
}

type EventHandler = (data: Record<string, unknown>, action: string) => Promise<void>;

const handlers: Record<string, EventHandler> = {
  Issue: handleIssueEvent,
  IssueComment: handleCommentEvent,
  Project: handleProjectEvent,
  Cycle: handleCycleEvent,
};

export async function processLinearEvent(payload: LinearWebhookPayload) {
  const handler = handlers[payload.type];

  if (!handler) {
    console.log(`No handler for event type: ${payload.type}`);
    return;
  }

  try {
    await handler(payload.data, payload.action);
  } catch (error) {
    console.error(`Error processing ${payload.type} event:`, error);
    throw error;
  }
}

async function handleIssueEvent(data: Record<string, unknown>, action: string) {
  const issue = data as {
    id: string;
    identifier: string;
    title: string;
    state: { name: string };
    priority: number;
    team: { key: string };
  };

  console.log(`Issue ${action}: ${issue.identifier} - ${issue.title}`);

  switch (action) {
    case "create":
      await onIssueCreated(issue);
      break;
    case "update":
      await onIssueUpdated(issue);
      break;
    case "remove":
      await onIssueRemoved(issue.id);
      break;
  }
}

async function handleCommentEvent(data: Record<string, unknown>, action: string) {
  const comment = data as {
    id: string;
    body: string;
    issue: { identifier: string };
    user: { name: string };
  };

  console.log(`Comment ${action} on ${comment.issue.identifier} by ${comment.user.name}`);
}

async function handleProjectEvent(data: Record<string, unknown>, action: string) {
  console.log(`Project ${action}:`, data);
}

async function handleCycleEvent(data: Record<string, unknown>, action: string) {
  console.log(`Cycle ${action}:`, data);
}

ステップ 3: ビジネスロジックハンドラー

// lib/linear-handlers.ts
import { sendSlackNotification } from "./slack";
import { syncToDatabase } from "./database";

async function onIssueCreated(issue: any) {
  // Sync to local database
  await syncToDatabase("issues", issue.id, issue);

  // Notify Slack for high-priority issues
  if (issue.priority <= 2) {
    await sendSlackNotification({
      channel: "#engineering-alerts",
      text: `New high-priority issue: ${issue.identifier} - ${issue.title}`,
    });
  }
}

async function onIssueUpdated(issue: any) {
  // Update local cache
  await syncToDatabase("issues", issue.id, issue);

  // Check for state changes
  if (issue.state?.name === "Done") {
    await celebrateCompletion(issue);
  }
}

async function onIssueRemoved(issueId: string) {
  await syncToDatabase("issues", issueId, null); // Soft delete
}

async function celebrateCompletion(issue: any) {
  console.log(`Issue completed: ${issue.identifier}`);
}

ステップ 4: Linear に Webhook を登録

# Linear UI を使用する場合:
# 1. Settings > API > Webhooks に移動
# 2. 「Create webhook」をクリック
# 3. エンドポイント URL を入力
# 4. 受信するイベントを選択
# 5. 保存して署名秘密鍵をコピー
// または API 経由
import { LinearClient } from "@linear/sdk";

async function createWebhook() {
  const client = new LinearClient({
    apiKey: process.env.LINEAR_API_KEY!,
  });

  const result = await client.createWebhook({
    url: "https://your-domain.com/api/webhooks/linear",
    label: "My Integration Webhook",
    teamId: "your-team-id", // Optional: limit to specific team
    resourceTypes: ["Issue", "IssueComment", "Project"],
  });

  if (result.success) {
    const webhook = await result.webhook;
    console.log("Webhook created:", webhook?.id);
    console.log("Secret (save this!):", webhook?.secret);
  }
}

ステップ 5: ngrok を使用したローカル開発

# ローカルサーバーを起動
npm run dev  # localhost:3000 で実行

# 別のターミナルで ngrok を起動
ngrok http 3000

# https URL をコピーして Linear webhook 設定に追加
# 例: https://abc123.ngrok.io/api/webhooks/linear

ステップ 6: べき等なイベント処理

// lib/idempotency.ts
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL);

export async function processIdempotent(
  webhookId: string,
  processor: () => Promise<void>
): Promise<boolean> {
  const key = `webhook:${webhookId}`;

  // Check if already processed
  const exists = await redis.exists(key);
  if (exists) {
    console.log(`Webhook ${webhookId} already processed, skipping`);
    return false;
  }

  // Mark as processing
  await redis.setex(key, 86400, "processing"); // 24 hour TTL

  try {
    await processor();
    await redis.setex(key, 86400, "completed");
    return true;
  } catch (error) {
    await redis.del(key); // Allow retry
    throw error;
  }
}

// Usage in webhook handler
await processIdempotent(payload.webhookId, async () => {
  await processLinearEvent(payload);
});

エラーハンドリング

エラー原因解決策
Invalid signature秘密鍵が間違っているか改ざんされているwebhook 秘密鍵を確認
Timeout処理が遅い非同期キューを使用
Duplicate eventsWebhook リトライべき等性を実装
Missing data部分的なイベントグレースフルに処理

リソース

次のステップ

linear-performance-tuning でパフォーマンスを最適化します。

ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ

詳細情報

作者
Brmbobo
リポジトリ
Brmbobo/Web2podcast
ライセンス
MIT
最終更新
2026/1/26

Source: https://github.com/Brmbobo/Web2podcast / ライセンス: MIT

関連スキル

汎用ソフトウェア開発⭐ リポ 39,967

doubt-driven-development

重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 1,175

apprun-skills

TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。

by yysun
OpenAIソフトウェア開発⭐ リポ 797

desloppify

コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。

by Git-on-my-level
汎用ソフトウェア開発⭐ リポ 39,967

debugging-and-error-recovery

テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

test-driven-development

テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

incremental-implementation

変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。

by addyosmani
本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: Brmbobo · Brmbobo/Web2podcast · ライセンス: MIT