Tutorial6 min read

How to Extract Action Items from Meeting Transcripts with an LLM

Use Gregnote webhooks and an LLM to automatically identify action items, owners, and deadlines from every meeting — and push them to Jira, Linear, or Notion.

Gregnote Team

4 August 2026

Why action items get lost

Most meeting notes are either missing or forgotten within 24 hours. Action items buried in a transcript nobody reads don't drive outcomes. Automatically extracting them — and dropping them into the tools your team already uses — closes that gap.

The extraction prompt

The key to reliable extraction is a structured prompt that produces machine-readable output:

const EXTRACT_PROMPT = `
You are an assistant that extracts action items from meeting transcripts.

Return a JSON array. Each item must have: - "task": a concise description of what needs to be done - "owner": the name of the person responsible (or null if unclear) - "due": a due date if mentioned (ISO format, or null)

Only include concrete commitments — not general discussion. Return [] if there are no action items. ;

Calling the LLM from your webhook handler

async function extractActionItems(segments) {
  const transcript = segments
    .filter(s => !s.is_bot)
    .map(s => `${s.speaker}: ${s.text}`)
    .join("\n");

const response = await openai.chat.completions.create({ model: "gpt-4o", response_format: { type: "json_object" }, messages: [ { role: "system", content: EXTRACT_PROMPT }, { role: "user", content: transcript }, ], });

return JSON.parse(response.choices[0].message.content); } ```

Pushing to Linear

async function createLinearIssues(actionItems, meetingTitle) {
  for (const item of actionItems) {
    await linearClient.createIssue({
      title: item.task,
      description: `Action item from: ${meetingTitle}`,
      assigneeId: await resolveLinearUserId(item.owner),
      dueDate: item.due,
    });
  }
}

Full webhook handler

app.post("/hooks/gregnote", async (req, res) => {
  const { event, transcript } = JSON.parse(req.body);
  if (event !== "meeting.completed") return res.sendStatus(200);

const items = await extractActionItems(transcript.segments); if (items.length > 0) { await createLinearIssues(items, "Meeting " + new Date().toLocaleDateString()); }

res.sendStatus(200); }); ```

What you get

Every meeting your bot attends now produces a set of Linear issues — automatically assigned to the right people — within a minute of the meeting ending. No copy-pasting, no missed commitments.

Try it yourself

API key in 30 seconds. Free credit on sign-up. No card required.