AutomationFlowsWeb Scraping › Turn Meeting Transcripts or Audio Into Google Docs Action Summaries with…

Turn Meeting Transcripts or Audio Into Google Docs Action Summaries with…

Original n8n title: Turn Meeting Transcripts or Audio Into Google Docs Action Summaries with Groq Whisper, Openrouter, and Google Docs

BySPCTEK AI @spctek-ai on n8n.io

Turn raw meeting transcripts or audio recordings into structured, ready-to-share Google Docs — automatically. No manual note-taking, no copy-pasting.

Webhook trigger★★★★☆ complexity15 nodesHTTP RequestGoogle Docs
Web Scraping Trigger: Webhook Nodes: 15 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #15499 — we link there as the canonical source.

This workflow follows the Google Docs → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "5cd67a50-9bbf-45d7-9fbe-1e488ba9de38",
      "name": "Step 2 \u2014 Groq Whisper Info",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1648,
        688
      ],
      "parameters": {
        "color": 6,
        "width": 360,
        "height": 292,
        "content": "**\ud83c\udf99\ufe0f STEP 2 \u2014 GROQ SETUP (Audio Transcription)**\n\nFree tier: 28,800 seconds/day \u2014 no card needed!\n\n1. Go to console.groq.com \u2192 API Keys \u2192 Create Key\n2. In n8n \u2192 Settings \u2192 Credentials \u2192 New \u2192 **Header Auth**\n   - Name: `Authorization`\n   - Value: `Bearer YOUR_GROQ_KEY_HERE`\n3. In the **Transcribe Audio** node \u2192 select this credential\n\nSupported formats: .mp3 .mp4 .wav .m4a .webm\nModel: whisper-large-v3-turbo (fast + accurate)"
      },
      "typeVersion": 1
    },
    {
      "id": "e79e4026-e39a-40f6-b295-3cb156de4d12",
      "name": "Step 3 \u2014 OpenRouter Info",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2160,
        688
      ],
      "parameters": {
        "color": 5,
        "width": 360,
        "height": 256,
        "content": "**\ud83e\udd16 STEP 3 \u2014 OPENROUTER SETUP (AI Summarization)**\n\n1. Go to openrouter.ai \u2192 Keys \u2192 Create Key\n2. In n8n \u2192 Settings \u2192 Credentials \u2192 New \u2192 **Header Auth**\n   - Name: `Authorization`\n   - Value: `Bearer YOUR_OPENROUTER_KEY_HERE`\n3. In the **Call AI API** node \u2192 select this credential\n\nFree models available!\nRecommended: openai/gpt-4o-mini"
      },
      "typeVersion": 1
    },
    {
      "id": "6d1a11b5-fcf0-4b2a-9528-7d97e1fbe9e2",
      "name": "Step 4 \u2014 Google Docs Info",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2656,
        688
      ],
      "parameters": {
        "color": 5,
        "width": 360,
        "height": 252,
        "content": "**\ud83d\udcc4 STEP 4 \u2014 GOOGLE DOCS SETUP**\n\n1. In n8n \u2192 Settings \u2192 Credentials \u2192 New \u2192 Google Docs OAuth2\n2. Sign in with your Google account and allow access\n3. Select this credential in both Google Docs nodes\n\nThe summary doc will be created in your Google Drive root folder. You can move or rename it afterwards."
      },
      "typeVersion": 1
    },
    {
      "id": "99cba79f-09fe-4776-8e8d-8216d5d242de",
      "name": "Transcribe Audio",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1664,
        1248
      ],
      "parameters": {
        "url": "https://api.groq.com/openai/v1/audio/transcriptions",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "multipart-form-data",
        "authentication": "genericCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "file",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "audio"
            },
            {
              "name": "model",
              "value": "whisper-large-v3-turbo"
            },
            {
              "name": "response_format",
              "value": "json"
            }
          ]
        },
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "ce687da7-b142-4095-9fa2-621d7d2faee6",
      "name": "Prepare Audio Input",
      "type": "n8n-nodes-base.code",
      "position": [
        1872,
        1248
      ],
      "parameters": {
        "jsCode": "const transcription = $input.first().json.text;\n\nif (!transcription || transcription.trim() === '') {\n  throw new Error('Audio transcription failed or returned empty. Check your audio file format (.mp3, .mp4, .wav, .m4a)');\n}\n\nconst webhookData = $('Receive Meeting Transcript').first().json;\nconst body = webhookData.body || {};\n\nconst meetingTitle = body.meeting_title || 'Untitled Meeting';\nconst meetingDate = body.meeting_date || new Date().toISOString().split('T')[0];\n\nreturn [{\n  json: {\n    transcript: transcription.trim().replaceAll('\\n', ' '),\n    meeting_title: meetingTitle,\n    meeting_date: meetingDate\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "60cbf23c-119e-4ddc-8262-9ace39ed5f6c",
      "name": "Call AI API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2160,
        1120
      ],
      "parameters": {
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"model\": \"openai/gpt-4o-mini\",\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are an expert meeting analyst. Read the meeting transcript and return a structured JSON with exactly these 4 keys:\\n- summary: string (2-3 sentence high-level overview)\\n- decisions: array of strings (each decision made)\\n- action_items: array of objects with keys: task (string), owner (string or TBD), deadline (string or Not specified)\\n- next_agenda: array of strings (suggested topics for next meeting)\\n\\nRespond with ONLY the raw JSON object. No markdown, no code fences, no extra text.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"Meeting Title: {{ $json.meeting_title }}\\nMeeting Date: {{ $json.meeting_date }}\\n\\nTranscript:\\n{{ $json.transcript }}\"\n    }\n  ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Title",
              "value": "Meeting Summary Workflow"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "8a0c9a71-d42b-41ff-bac5-297fc1eba1ff",
      "name": "Step 1 \u2014 Trigger Info1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1216,
        688
      ],
      "parameters": {
        "color": 5,
        "width": 344,
        "height": 380,
        "content": "**\u2699\ufe0f STEP 1 \u2014 TRIGGER SETUP**\n\n**\ud83d\udcdd Text Input:**\nSend POST with JSON body:\n```json\n{\n  \"transcript\": \"Full meeting transcript...\",\n  \"meeting_title\": \"Weekly Sync\",\n  \"meeting_date\": \"2025-05-05\"\n}\n```\n\n**\ud83c\udf99\ufe0f Audio Input:**\nSend POST with multipart/form-data:\n- `audio` \u2192 your audio file\n- `meeting_title` \u2192 optional\n- `meeting_date` \u2192 optional\n\nWorkflow auto-detects which type was sent!"
      },
      "typeVersion": 1
    },
    {
      "id": "f708e8ea-ce08-491c-bcff-60e529eadf57",
      "name": "Receive Meeting Transcript",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1216,
        1120
      ],
      "parameters": {
        "path": "meeting-summary",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "e704c843-cf60-401e-8f26-0f5bcb6860f1",
      "name": "Text or Audio?",
      "type": "n8n-nodes-base.if",
      "position": [
        1408,
        1120
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "606f2461-5dd2-4dc5-9235-0eab499d74e3",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.body.transcript }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "425873e9-392c-4439-94d3-c7e74d508133",
      "name": "Validate & Prepare Input",
      "type": "n8n-nodes-base.code",
      "position": [
        1760,
        1008
      ],
      "parameters": {
        "jsCode": "const body = $input.first().json.body;\n\nif (!body || !body.transcript || body.transcript.trim() === '') {\n  throw new Error('Missing required field: transcript. Please send your meeting transcript in the POST body as JSON.');\n}\n\nconst meetingTitle = body.meeting_title || 'Untitled Meeting';\nconst meetingDate = body.meeting_date || new Date().toISOString().split('T')[0];\nconst transcript = body.transcript.trim().replaceAll('\\n', ' ');\n\nreturn [{\n  json: {\n    transcript,\n    meeting_title: meetingTitle,\n    meeting_date: meetingDate\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "be558b97-9bf7-4760-bdf0-e571ff2e0b5f",
      "name": "Format Document Content",
      "type": "n8n-nodes-base.code",
      "position": [
        2384,
        1120
      ],
      "parameters": {
        "jsCode": "const rawContent = $input.first().json.choices[0].message.content;\n\nlet parsed;\ntry {\n  const clean = rawContent.replace(/```json|```/g, '').trim();\n  parsed = JSON.parse(clean);\n} catch (e) {\n  throw new Error('AI returned invalid JSON. Raw response: ' + rawContent);\n}\n\n// Get meeting meta \u2014 works for both text and audio paths\nlet meetingTitle = 'Untitled Meeting';\nlet meetingDate = new Date().toISOString().split('T')[0];\n\ntry {\n  const textData = $('Validate & Prepare Input').first().json;\n  meetingTitle = textData.meeting_title || meetingTitle;\n  meetingDate = textData.meeting_date || meetingDate;\n} catch (e) {\n  try {\n    const audioData = $('Prepare Audio Input').first().json;\n    meetingTitle = audioData.meeting_title || meetingTitle;\n    meetingDate = audioData.meeting_date || meetingDate;\n  } catch (e2) {\n    // use defaults set above\n  }\n}\n\nconst docTitle = `Meeting Summary \u2014 ${meetingTitle} \u2014 ${meetingDate}`;\n\n// Format Action Items\nlet actionItemsText = '';\nif (Array.isArray(parsed.action_items) && parsed.action_items.length > 0) {\n  parsed.action_items.forEach((item, i) => {\n    actionItemsText += `${i + 1}. ${item.task}\\n   Owner: ${item.owner || 'TBD'}\\n   Deadline: ${item.deadline || 'Not specified'}\\n\\n`;\n  });\n} else {\n  actionItemsText = 'No action items identified.\\n';\n}\n\n// Format Decisions\nlet decisionsText = '';\nif (Array.isArray(parsed.decisions) && parsed.decisions.length > 0) {\n  parsed.decisions.forEach(d => { decisionsText += `\u2022 ${d}\\n`; });\n} else {\n  decisionsText = 'No explicit decisions recorded.\\n';\n}\n\n// Format Next Agenda\nlet agendaText = '';\nif (Array.isArray(parsed.next_agenda) && parsed.next_agenda.length > 0) {\n  parsed.next_agenda.forEach((a, i) => { agendaText += `${i + 1}. ${a}\\n`; });\n} else {\n  agendaText = 'No agenda suggestions.\\n';\n}\n\nconst docContent = `MEETING SUMMARY\\n${'='.repeat(50)}\\nMeeting: ${meetingTitle}\\nDate: ${meetingDate}\\nGenerated: ${new Date().toLocaleString()}\\n\\n${'\u2500'.repeat(50)}\\n\\n\ud83d\udccc EXECUTIVE SUMMARY\\n${'\u2500'.repeat(50)}\\n${parsed.summary || 'No summary generated.'}\\n\\n${'\u2500'.repeat(50)}\\n\\n\u2705 DECISIONS MADE\\n${'\u2500'.repeat(50)}\\n${decisionsText}\\n${'\u2500'.repeat(50)}\\n\\n\ud83d\udccb ACTION ITEMS\\n${'\u2500'.repeat(50)}\\n${actionItemsText}${'\u2500'.repeat(50)}\\n\\n\ud83d\uddd3\ufe0f NEXT MEETING AGENDA\\n${'\u2500'.repeat(50)}\\n${agendaText}\\n${'\u2500'.repeat(50)}\\nAuto-generated by n8n | Meeting Summary Workflow`;\n\nreturn [{\n  json: {\n    doc_title: docTitle,\n    doc_content: docContent,\n    meeting_title: meetingTitle,\n    meeting_date: meetingDate,\n    summary: parsed.summary,\n    decisions: parsed.decisions,\n    action_items: parsed.action_items,\n    next_agenda: parsed.next_agenda,\n    action_items_count: Array.isArray(parsed.action_items) ? parsed.action_items.length : 0\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "15812d76-6bb0-438f-9905-66f8c1ba9b33",
      "name": "Write Summary to Doc",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        2816,
        1120
      ],
      "parameters": {
        "actionsUi": {
          "actionFields": [
            {
              "text": "={{ $('Format Document Content').first().json.doc_content }}",
              "action": "insert"
            }
          ]
        },
        "operation": "update",
        "documentURL": "={{ $json.id }}"
      },
      "credentials": {
        "googleDocsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "b5d098de-7750-42da-bc2f-ef57a92f9a53",
      "name": "Create Google Doc",
      "type": "n8n-nodes-base.googleDocs",
      "position": [
        2608,
        1120
      ],
      "parameters": {
        "title": "={{ $json.doc_title }}",
        "folderId": "1XC8faFgUSySqlfST6tjXVAPJDSaCQxUq"
      },
      "credentials": {
        "googleDocsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "8683ee43-7d40-41c3-b828-93340906c259",
      "name": "Return Doc Link",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3040,
        1120
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({\n  success: true,\n  message: 'Meeting summary created successfully',\n  doc_title: $('Format Document Content').first().json.doc_title,\n  google_doc_id: $('Create Google Doc').first().json.id,\n  google_doc_url: 'https://docs.google.com/document/d/' + $('Create Google Doc').first().json.id + '/edit',\n  summary_preview: $('Format Document Content').first().json.summary,\n  action_items_count: $('Format Document Content').first().json.action_items_count\n}) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "7aba0f63-e450-4238-8eca-b291b1190317",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        544,
        688
      ],
      "parameters": {
        "width": 624,
        "height": 560,
        "content": "## \ud83d\udccb Meeting Summary \u2192 Action Items \n\n**Who is this for?**\nAnyone who wants to turn raw meeting transcripts OR audio recordings into structured, ready-to-use summaries \u2014 without manually writing notes.\n\n**What it does:**\nSend transcript text OR upload an audio file \u2192 AI generates a clean Google Doc with:\n- \u2705 Executive Summary (2\u20133 lines)\n- \u2705 Key Decisions Made\n- \u2705 Action Items (numbered, with Owner & Deadline)\n- \u2705 Suggested Next Meeting Agenda\n\n**Supports Two Input Types:**\n\ud83d\udcdd Text \u2014 Paste transcript as JSON\n\ud83c\udf99\ufe0f Audio \u2014 Upload .mp3 / .mp4 / .wav / .m4a (auto-transcribed via Groq Whisper)\n\n**How it works:**\n1. Send POST request with text OR audio file\n2. Workflow auto-detects input type\n3. Audio is transcribed via Groq Whisper (free, 28,800 sec/day)\n4. AI structures content into 4 sections\n5. Google Doc is created and link returned\n\n**Requirements:**\n- Groq API Key \u2192 stored as Header Auth credential (free)\n- OpenRouter API Key \u2192 stored as Header Auth credential\n- Google Docs OAuth2 credentials"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Call AI API": {
      "main": [
        [
          {
            "node": "Format Document Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Text or Audio?": {
      "main": [
        [
          {
            "node": "Validate & Prepare Input",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Transcribe Audio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transcribe Audio": {
      "main": [
        [
          {
            "node": "Prepare Audio Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Google Doc": {
      "main": [
        [
          {
            "node": "Write Summary to Doc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Audio Input": {
      "main": [
        [
          {
            "node": "Call AI API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write Summary to Doc": {
      "main": [
        [
          {
            "node": "Return Doc Link",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Document Content": {
      "main": [
        [
          {
            "node": "Create Google Doc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate & Prepare Input": {
      "main": [
        [
          {
            "node": "Call AI API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive Meeting Transcript": {
      "main": [
        [
          {
            "node": "Text or Audio?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Turn raw meeting transcripts or audio recordings into structured, ready-to-share Google Docs — automatically. No manual note-taking, no copy-pasting.

Source: https://n8n.io/workflows/15499/ — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

📘 Description This workflow is a fully automated contract generation and delivery system that converts structured client input into a finalized, professional PDF contract and uploads it directly to yo

Google Drive, Google Docs, HTTP Request
Web Scraping

This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di

n8n, Execute Workflow Trigger, HTTP Request +1
Web Scraping

This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .

HTTP Request, Ssh
Web Scraping

This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c

HTTP Request
Web Scraping

Sign PDF documents with legally-compliant digital signatures using X.509 certificates. Supports multiple PAdES signature levels (B, T, LT, LTA) with optional visible stamps.

Execute Command, HTTP Request, Read Write File +1