Recording a meeting is easy. What happens after is not. A one-hour meeting takes two to three hours to transcribe by hand, one person types up the minutes, action items scatter across chat logs with no owner, and next week's meeting opens with "did anyone follow up on that?" -- silence. The recording sits on a drive, as useless as if it never existed.
The problem is not "did we record" but "nobody turned the recording into executable tasks afterward." This SOP builds an n8n workflow that automates the full chain: audio upload -> Whisper transcription -> LLM-generated minutes with action items (owner + deadline) -> distribution to Feishu, email, and Notion. Node names and API parameters follow official docs -- nothing fabricated.
One-line framing: this workflow does not solve "transcription" (Whisper already does that well) -- it solves the "structuring" that comes after. It turns a wall of text into minutes with a summary, decisions, owners, and deadlines, then pushes them where they need to go.
One: Scenario and Pain Points
The typical team meeting chain: record -> one person writes minutes -> posts in group chat -> everyone glances -> action items go unowned -> next week's meeting re-discusses the same thing. Four pain points dominate:
| Pain point | Symptom | Consequence |
|---|---|---|
| Manual transcription | One hour of audio takes 2-3 hours to process | Nobody volunteers; minutes arrive late or never |
| Unstructured minutes | A wall of text, no clear points | Reading it is pointless; key info is buried |
| Ownerless action items | "Follow up" and "advance" with no name attached | No owner = no execution |
| Manual distribution | Post to chat, then forget | Tasks never enter any tracking system |
The core gap: recording ≠ minutes, minutes ≠ executable tasks. Two steps are missing -- turning audio into text, then turning text into structured tasks with owners and deadlines. n8n + Whisper + LLM fills both.
Two: Tool Selection -- n8n Orchestration + Whisper Transcription + LLM Extraction
Three core components, each owning one segment of the chain:
| Component | What it does | Why this choice |
|---|---|---|
| n8n | Workflow orchestration: trigger -> transcribe -> generate -> distribute | Visual, self-hostable, data stays private, 400+ nodes for Feishu/email/Notion |
| OpenAI Whisper | Speech to text | Official ASR, $0.006/min, supports Chinese, stable API |
| LLM (GPT-4o / DeepSeek) | Generate minutes + extract action items | Extracts summaries, decisions, tasks, owners, deadlines from unstructured text |
Why not use a ready-made meeting bot (Otter, Fireflies, Feishu Minutes)?
Not because they are bad -- but in three scenarios, self-building wins:
- Control: Ready-made tools output a fixed format. If you need "every action item must have an owner + deadline + @-mention on Feishu," they cannot do it. A custom prompt gives you full control over output format.
- Cost: Otter Business is $20/month/user -- 10 people is $200/month. Whisper charges $0.36 per hour of audio; 50 hours of meetings a month is about $18. An order of magnitude cheaper.
- Data privacy: Self-hosted n8n + your choice of LLM means recordings never touch a third-party SaaS. For internal or sensitive meetings, this is a hard requirement.
Ready-made tools are fine for "personal use, non-sensitive, format-agnostic" scenarios. For teams that need custom output and private deployment, a self-built workflow is the better call.
Three: Step-by-Step Build
Four steps, each with node configuration details and key parameters. The following uses self-hosted n8n; cloud operations are identical unless noted.
Step 1: Recording Upload Trigger (Webhook Node)
Drag in a Webhook node as the workflow entry point. Audio files arrive via HTTP POST to this webhook.
Node configuration:
| Parameter | Value | Notes |
|---|---|---|
| HTTP Method | POST | File upload requires POST |
| Path | meeting-upload | Path segment of the webhook URL |
| Response Mode | When Last Node Finishes | Wait for the workflow to complete before responding |
| Response Data | All Entries | Return processing result to the uploader |
When the Webhook node receives a file, n8n stores the binary data in a binary property (default property name depends on the upload field name, typically data). Downstream nodes reference the audio file by this property name.
# Upload a recording to trigger the webhook (curl test)
curl -X POST https://your-n8n-domain/webhook/meeting-upload \
-F "file=@meeting_20260806.mp3"If n8n runs on an internal network, the Webhook must be publicly reachable. Use an ngrok tunnel or reverse proxy to expose it. In production, always use HTTPS and access control.
Alternative: Local File Trigger node. If recordings land in a shared folder automatically (e.g., Zoom auto-records to a directory), use the Local File Trigger node instead of Webhook. Configure the watch path and trigger event (file created) to skip manual upload. Self-hosted only.
Step 2: HTTP Request to Whisper for Transcription
Drag in an HTTP Request node, connect it to the Webhook output. This calls the OpenAI Whisper API to convert audio to text.
Whisper API spec (per official docs):
- Endpoint:
POST https://api.openai.com/v1/audio/transcriptions - Auth:
Authorization: Bearer sk-xxx(store via n8n Credentials, do not hardcode) - Required params:
file(audio file),model(whisper-1) - Optional params:
language(ISO-639-1, e.g.zh,en),response_format(json/text/srt/verbose_json/vtt),prompt(context/proper nouns),temperature(0-1) - File size limit: 25 MB
- Supported formats: mp3, mp4, mpeg, mpga, m4a, wav, webm
HTTP Request node configuration:
| Parameter | Value |
|---|---|
| Method | POST |
| URL | https://api.openai.com/v1/audio/transcriptions |
| Authentication | Generic Credential Type -> OpenAI API (credential reference, not hardcoded) |
| Body Content Type | Multipart-Form Data |
Input Field model | whisper-1 (type: String) |
Input Field file | Type: File, Binary Property set to the Webhook's property name (e.g. data) |
Input Field response_format | verbose_json (type: String) |
Input Field language | zh (type: String, for Chinese meetings) |
// verbose_json response example (includes timestamped segments for speaker diarization)
{
"task": "transcribe",
"language": "chinese",
"duration": 3600.12,
"text": "Today's meeting mainly discussed the Q3 product roadmap...",
"segments": [
{ "id": 0, "start": 0.0, "end": 5.32, "text": "Today's meeting mainly discussed..." },
{ "id": 1, "start": 5.32, "end": 12.10, "text": "..." }
]
}Why verbose_json over the default json: it returns both text (full transcript) and segments (timestamped chunks). If you later need speaker diarization or timestamp annotation, segments are the foundation.
Cost reference: Whisper charges $0.006/min. A one-hour meeting costs about $0.36. See the OpenAI pricing page for current rates.
Step 3: LLM Node to Generate Minutes + Extract Action Items
With the full transcript in hand, use n8n's Basic LLM Chain node (Advanced AI node family) to generate structured minutes. This node takes a prompt and returns LLM output -- no Memory or Tools needed, making it lighter than the AI Agent node and ideal for single-shot "input text -> structured result" tasks.
Node configuration:
- Drag in a Basic LLM Chain node, connect it to the HTTP Request (Whisper) output.
- Attach an OpenAI Chat Model sub-node as the Language Model: select
gpt-4o(or a DeepSeek-compatible model), reference the API key via credentials. - In the Basic LLM Chain prompt, reference the transcript:
{{ $json.text }}(the full text from Whisper's verbose_json response).
System Prompt (the critical part):
You are a meeting minutes assistant. Given a meeting transcript, output Markdown in this structure:
## Meeting Minutes
### Summary
(3-5 sentences capturing the core content)
### Key Decisions
1. (One decision per line)
### Action Items
| Task | Owner | Deadline |
|---|---|---|
| (Specific, executable task) | (Name; "TBD" if unclear) | (Date; "TBD" if unclear) |
### Open Issues
- (Discussed but unresolved topics)
Rules:
- Summary: max 5 sentences, conclusions only
- Action items must be concrete actions, not vague phrases like "advance X"
- Extract owners and deadlines from the text; mark "TBD" if not explicitly stated -- never guess
- Do not fabricate information not present in the textAdvanced: Use a Code node to split action items. If you need to push each action item individually to Feishu (e.g., @-mention the owner), add a Code node after the LLM. Have the LLM output JSON instead of Markdown, then parse it into an array:
// Code node: parse LLM JSON output, split into individual items
const raw = $input.first().json.text; // LLM output
const parsed = JSON.parse(raw);
const actionItems = parsed.action_items || [];
// Output each action item as a separate item for downstream per-item distribution
return actionItems.map(item => ({
json: {
task: item.task,
owner: item.owner,
deadline: item.deadline,
full_minutes: parsed.summary // attach full minutes
}
}));The Split Out node achieves the same result; the Code node is more flexible.
Step 4: Distribute to Feishu / Email / Notion
Once minutes and action items are generated, distribute them through your team's preferred channels. Three common routes:
① Feishu (HTTP Request to Feishu custom bot webhook)
n8n has no native Feishu node. Use an HTTP Request to call a Feishu custom bot's Incoming Webhook. First, add a custom bot in your Feishu group and copy the webhook URL.
// HTTP Request node -> Feishu webhook
// Method: POST
// URL: https://open.feishu.cn/open-apis/bot/v2/hook/xxx
// Headers: Content-Type: application/json
// Body (JSON):
{
"msg_type": "interactive",
"card": {
"header": {
"title": { "tag": "plain_text", "content": "Meeting Minutes - {{ $json.date }}" }
},
"elements": [
{
"tag": "markdown",
"content": "{{ $json.minutes }}"
}
]
}
}② Email (Email Send node)
n8n's built-in Email Send node sends via SMTP. Configure SMTP credentials (Gmail, corporate mail, etc.), then reference the LLM output in the recipient, subject, and body fields using expressions.
| Parameter | Value |
|---|---|
| From | [email protected] |
| To | [email protected] (or extract from attendee list) |
| Subject | Meeting Minutes - {{ $now.toFormat('yyyy-MM-dd') }} |
| Email Type | HTML |
| Message | {{ $json.minutes }} (LLM-generated Markdown, convert to HTML with marked) |
③ Notion (Notion node for archiving)
n8n's built-in Notion node writes minutes into a Notion database for searchability and tracking. Configure Notion credentials (OAuth or Internal Integration Token), select Create Database Page, specify the Database ID, and map summary and action items to the corresponding properties.
| Parameter | Value |
|---|---|
| Resource | Database Page |
| Operation | Create |
| Database ID | Your meeting-minutes database ID |
| Properties | Title: {{ $json.title }}, Date: {{ $json.date }}, Owner: {{ $json.owner }} |
| Content | {{ $json.minutes }} (written to the page body) |
Distribution recommendation: push action items to Feishu (@-mention owners for instant reach) + archive full minutes in Notion (searchable, traceable) + email a summary (fallback). Three channels cover "instant alert + long-term archive + passive notification."
Four: Pitfall Quick Reference
Pitfall 1: Poor audio quality, transcription full of errors. Whisper is highly accurate for clean speech, but background noise, far-field microphones, and overlapping voices cause frequent errors. Fix: use a near-field microphone or record directly from the meeting system (no speaker playback into a mic). Pre-process with ffmpeg noise reduction:
# ffmpeg noise reduction (self-hosted n8n can run via Execute Command node)
ffmpeg -i input.mp3 -af "afftdn=nr=10" output.mp3Pitfall 2: Long audio exceeds Whisper's 25 MB limit. A one-hour mp3 is 15-30 MB; wav is larger. It is easy to hit the ceiling. Fix: split the audio by time (e.g., 20-minute segments) with ffmpeg before uploading, call Whisper per segment, then concatenate the text:
# Split audio by duration (1200 seconds = 20 minutes per segment)
ffmpeg -i long_meeting.mp3 -f segment -segment_time 1200 -c copy chunk_%03d.mp3On self-hosted n8n, use the Execute Command node to run ffmpeg (per official docs; not available on cloud). Alternatively, pre-process on the upload side.
Pitfall 3: Transcript exceeds LLM token limit. A one-hour meeting yields roughly 8,000-15,000 Chinese characters. GPT-4o's context window handles this, but longer meetings or cheaper models may not. Fix: send Whisper segments in batches to the LLM, generate a summary per batch, then have a final LLM pass merge them. Or use a long-context model (GPT-4o 128K, DeepSeek 64K).
Pitfall 4: Action items extracted incorrectly -- wrong owners or missed items. The hard part of extracting action items from spoken language: "someone should follow up on that" does not name a person. Fix: (1) In the prompt, require "mark owner as TBD if unclear; never guess"; (2) have the LLM output JSON instead of free text for tighter structural constraints; (3) build a meeting culture of explicitly saying "Zhang San, finish X by Friday" -- solve it at the source.
Pitfall 5: Chinese proper nouns, names, and jargon transcribed wrong. Whisper handles common Chinese well but stumbles on internal names, product codenames, and industry terms. Fix: use Whisper's prompt parameter to provide context vocabulary. Place a reference text containing the proper nouns in the prompt -- Whisper uses it to adjust recognition:
// Whisper prompt parameter example (provide proper-noun context)
"prompt": "Names and terms that may appear in this meeting: Zhang Wei, Li Na, Project Alpha, Q3 OKR, Feishu Bitable."Pitfall 6: Speaker diarization. The Whisper API does not natively support speaker diarization -- the transcript is continuous text with no "Speaker A / Speaker B" labels. Fix: (1) Use verbose_json to get timestamped segments and coarsely split by silence gaps; (2) post-process with a third-party diarization service (e.g., pyannote.audio); (3) if you only need minutes (not verbatim transcript), let the LLM infer speakers from context during generation (limited effectiveness, works best for meetings with clear, distinct speakers).
FAQ
Q1: Can I use a transcription service other than OpenAI Whisper? A: Yes. Replace the HTTP Request in Step 2 with another transcription API -- Alibaba Cloud Speech-to-Text, Tencent Cloud ASR, and Azure Speech to Text all offer REST APIs callable via n8n's HTTP Request node with similar parameter structures. For Chinese scenarios, domestic ASR services may handle dialects and proper nouns better. Endpoints and parameters per each provider's docs.
Q2: Can I run this on n8n Cloud without self-hosting? A: Yes. n8n Cloud supports all core nodes, but the Local File Trigger and Execute Command nodes are unavailable (security restriction). Use Webhook triggers and pre-process audio on the upload side instead. Cloud webhooks are publicly reachable out of the box -- no ngrok needed.
Q3: How do I set up the Feishu bot webhook?
A: Feishu group -> Settings -> Group Bots -> Add Bot -> select "Custom Bot." Copy the webhook URL (format: https://open.feishu.cn/open-apis/bot/v2/hook/xxx) and paste it into the HTTP Request node's URL field. If signature verification is enabled, add timestamp and sign fields to the request body; the signing algorithm is in the Feishu Open Platform docs.
Q4: How do I handle meetings longer than one hour? A: Two-step approach -- first, split the audio by time (Pitfall 2), call Whisper per segment, and concatenate the full transcript. Then send segments to the LLM for per-segment summaries, with a final LLM pass to merge them. If using a long-context model like GPT-4o, a one-hour meeting's transcript (roughly 10,000-20,000 characters) usually fits within the context window and can be sent in one piece.
Q5: Can action items be auto-created in Jira or Feishu Tasks? A: Yes. After the Code node in Step 3 splits action items into individual items, connect n8n's Jira node or the Feishu Tasks API (via HTTP Request) to create a task per item. You need to configure the corresponding credentials and project ID. Specific node parameters per n8n docs.
Take
The essence of this workflow is not "AI replaces humans at taking minutes" -- it is filling in the "recording -> executable task" chain that should have been automated all along. Whisper solves "dictation," the LLM solves "structuring," and n8n solves "wiring it together to run automatically." All three are cheap: $0.36 for one hour of transcription plus one LLM call is orders of magnitude cheaper than having a person spend two hours on minutes.
The real difficulty is not building the workflow (dragging four nodes on the n8n canvas). It is in two places: audio quality (garbage in, garbage out -- no ASR saves a far-field noise recording) and prompt design (action-item accuracy lives or dies by prompt constraints). Get those two right, and the workflow will consistently produce minutes that are more uniform and less leaky than manual notes.
References
- n8n official docs: https://docs.n8n.io
- n8n Webhook node docs: https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/
- n8n HTTP Request node docs: https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/
- n8n Basic LLM Chain node docs: https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.chainllm/
- n8n GitHub (n8n-io/n8n): https://github.com/n8n-io/n8n
- OpenAI Whisper API reference: https://platform.openai.com/docs/api-reference/audio/createTranscription
- OpenAI speech-to-text guide: https://platform.openai.com/docs/guides/speech-to-text
- OpenAI pricing: https://openai.com/pricing
- Feishu custom bot: https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot
- n8n node names and Whisper API parameters verified against official docs