This workflow corresponds to n8n.io template #15422 — we link there as the canonical source.
This workflow follows the Agent → Form Trigger 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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "34ee3c1a-3fbf-4214-8a7b-5346c32da571",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2176,
-400
],
"parameters": {
"color": 4,
"width": 428,
"height": 1156,
"content": "## Sales Call Objection Extractor to Airtable Tracker \u2014 WayinVideo Find Moments + GPT-4o-mini + Airtable\n\nFor sales managers and revenue teams who want to automatically track objection patterns across their team's sales calls without manually reviewing every recording. This workflow uses the WayinVideo Find Moments API \u2014 not transcription \u2014 to scan the recording and find exact moments matching your search query, returning timestamps, descriptions, and relevance scores for each match. Submit a call recording URL and your objection search query via the form. WayinVideo finds up to 5 matching moments. GPT-4o-mini analyzes each moment and generates a structured coaching note \u2014 objection category, what happened, rep response assessment, coaching advice, and a suggested reframe in first person ready to use. Each moment is saved as a separate Airtable record with Status set to Needs Review.\n\n## How it works\n- **1. Form \u2014 Call Recording + Details** collects the recording URL, rep name, contact name, company, call purpose, date, and the objection search query\n- **2. WayinVideo \u2014 Submit Find Moments** submits the URL and query to the Find Moments API \u2014 up to 5 matching moments returned\n- **3. Wait \u2014 90 Seconds** gives the API initial processing time\n- **4. WayinVideo \u2014 Get Moments Results** polls the find-moments results endpoint\n- **5. IF \u2014 Find Moments Complete?** checks for SUCCEEDED \u2014 if not, retries via 30-second wait\n- **7. Code \u2014 Extract Objection Clips** reads the clips array, converts millisecond timestamps to MM:SS, and returns one item per clip\n- **8. AI Agent \u2014 Generate Coaching Note** uses GPT-4o-mini to analyze each clip's metadata and write a 5-section coaching analysis\n- **10. Code \u2014 Parse Coaching Output** extracts all 5 coaching sections via regex and assembles the Airtable record\n- **11. HTTP \u2014 Save to Airtable** creates one record per objection moment with all 16 fields and Status set to Needs Review\n\n## Set up steps\n1. In **2. WayinVideo \u2014 Submit Find Moments** and **4. WayinVideo \u2014 Get Moments Results** \u2014 replace YOUR_WAYINVIDEO_API_KEY\n2. In **9. OpenAI \u2014 GPT-4o-mini Model** \u2014 connect your OpenAI credential\n3. In **11. HTTP \u2014 Save to Airtable** \u2014 replace YOUR_AIRTABLE_API_KEY (Personal Access Token from airtable.com/create/tokens with data.records:write scope), YOUR_AIRTABLE_BASE_ID, and YOUR_AIRTABLE_TABLE_NAME\n4. Create an Airtable table named Sales Objections with fields: Call Date, Rep Name, Contact Name, Company, Call Purpose, Objection Type, Objection Category, Moment Title, Objection Description, Timestamp in Recording, Relevance Score, Rep Response Assessment, Coaching Note, Suggested Reframe, Recording URL, Status"
},
"typeVersion": 1
},
{
"id": "cbd36f74-afe3-45ff-a96b-43f4d2e10108",
"name": "Section \u2014 Form Input",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1728,
-208
],
"parameters": {
"color": 5,
"width": 292,
"height": 516,
"content": "## Form Input\nSales rep or manager submits recording URL, rep name, contact name, company, call purpose, call date, and the objection search query. The query directly drives which moments WayinVideo finds \u2014 be specific for better results."
},
"typeVersion": 1
},
{
"id": "48a4db2e-10d4-401b-b2fe-89af578faf47",
"name": "Section \u2014 Find Moments Submit and Poll",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1408,
-144
],
"parameters": {
"color": 6,
"width": 708,
"height": 452,
"content": "## WayinVideo Find Moments Submit and Poll\nSubmits the recording URL and search query to the Find Moments API \u2014 returning up to 5 matching moments with titles, descriptions, timestamps, and relevance scores. Waits 90 seconds then polls until SUCCEEDED."
},
"typeVersion": 1
},
{
"id": "ab7f652a-0f7c-4a8d-a0c8-838802f2f03d",
"name": "Section \u2014 Status Check and Retry Loop",
"type": "n8n-nodes-base.stickyNote",
"position": [
-640,
-144
],
"parameters": {
"color": 6,
"width": 260,
"height": 612,
"content": "## Find Moments Status Check and Retry Loop\nIF checks for SUCCEEDED status. TRUE proceeds to clip extraction. FALSE waits 30 seconds and polls again. Loop continues until results are ready."
},
"typeVersion": 1
},
{
"id": "9b1423ac-0bd3-4302-9475-32de2abceafb",
"name": "Section \u2014 Clip Extraction and AI Coaching Analysis",
"type": "n8n-nodes-base.stickyNote",
"position": [
-336,
-208
],
"parameters": {
"color": 6,
"width": 516,
"height": 708,
"content": "## Clip Extraction and AI Coaching Analysis\nSplits the clips array into one item per moment. GPT-4o-mini analyzes each clip using its title, description, and relevance score \u2014 generating objection category, what happened, rep response assessment, coaching note, and a first-person suggested reframe."
},
"typeVersion": 1
},
{
"id": "83d7d843-5145-4dff-9777-37c22c6a6415",
"name": "Section \u2014 Coaching Parse and Airtable Save",
"type": "n8n-nodes-base.stickyNote",
"position": [
240,
-112
],
"parameters": {
"color": 4,
"width": 548,
"height": 356,
"content": "## Coaching Parse and Airtable Save\nRegex extracts all 5 coaching sections and assembles the full Airtable record. One record created per objection moment with Status set to Needs Review for manager review."
},
"typeVersion": 1
},
{
"id": "d73590d7-83f8-4bec-bb06-cd6e405c6e60",
"name": "1. Form \u2014 Call Recording + Details",
"type": "n8n-nodes-base.formTrigger",
"position": [
-1632,
32
],
"parameters": {
"options": {},
"formTitle": "Sales Objection Extractor",
"formFields": {
"values": [
{
"fieldLabel": "Call Recording URL",
"placeholder": "https://zoom.us/rec/xxxxxxx or Loom/Google Meet link",
"requiredField": true
},
{
"fieldLabel": "Sales Rep Name",
"placeholder": "e.g. Sarah Johnson",
"requiredField": true
},
{
"fieldLabel": "Contact Name",
"placeholder": "e.g. John Smith",
"requiredField": true
},
{
"fieldLabel": "Company Name",
"placeholder": "e.g. Acme Corp",
"requiredField": true
},
{
"fieldLabel": "Call Purpose",
"placeholder": "e.g. Discovery Call, Demo, Pricing Call, Follow-up",
"requiredField": true
},
{
"fieldLabel": "Call Date",
"placeholder": "e.g. 2025-04-29",
"requiredField": true
},
{
"fieldLabel": "Objection Type to Search",
"placeholder": "e.g. pricing too expensive / competitor comparison / missing feature / timing not right",
"requiredField": true
}
]
},
"formDescription": "Paste your sales call recording URL. AI will find every objection moment, analyze how it was handled, and log a coaching note to Airtable."
},
"typeVersion": 2.2
},
{
"id": "a68ec47f-7810-4a5e-8556-a1298f7c3b50",
"name": "2. WayinVideo \u2014 Submit Find Moments",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1344,
32
],
"parameters": {
"url": "https://wayinvideo-api.wayin.ai/api/v2/clips/find-moments",
"method": "POST",
"options": {},
"jsonBody": "={\n \"video_url\": \"{{ $json['Call Recording URL'] }}\",\n \"query\": \"{{ $json['Objection Type to Search'] }}\",\n \"project_name\": \"Objection \u2014 {{ $json['Contact Name'] }} | {{ $json['Company Name'] }}\",\n \"limit\": 5,\n \"enable_export\": false,\n \"target_lang\": \"en\"\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_TOKEN_HERE"
},
{
"name": "x-wayinvideo-api-version",
"value": "v2"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "50c5e721-1b88-4adc-884b-3cd21c99c43e",
"name": "3. Wait \u2014 90 Seconds",
"type": "n8n-nodes-base.wait",
"position": [
-1072,
32
],
"parameters": {
"amount": 90
},
"typeVersion": 1.1
},
{
"id": "7afff640-07d3-4330-b0f4-c0e9e99f0ecd",
"name": "4. WayinVideo \u2014 Get Moments Results",
"type": "n8n-nodes-base.httpRequest",
"position": [
-832,
32
],
"parameters": {
"url": "=https://wayinvideo-api.wayin.ai/api/v2/clips/find-moments/results/{{ $('2. WayinVideo \u2014 Submit Find Moments').item.json.data.id }}",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_TOKEN_HERE"
},
{
"name": "x-wayinvideo-api-version",
"value": "v2"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "061ecb9b-a3bc-4ef9-bde5-e4bf0fc94cf7",
"name": "5. IF \u2014 Find Moments Complete?",
"type": "n8n-nodes-base.if",
"position": [
-592,
32
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "status-check",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.data.status }}",
"rightValue": "SUCCEEDED"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "baba7e5c-6d05-4ca3-b9fe-4ae559a88563",
"name": "6. Wait \u2014 30 Seconds Retry",
"type": "n8n-nodes-base.wait",
"position": [
-592,
224
],
"parameters": {
"amount": 30
},
"typeVersion": 1.1
},
{
"id": "2ea43199-b48b-4311-a782-7acf0ba7c301",
"name": "7. Code \u2014 Extract Objection Clips",
"type": "n8n-nodes-base.code",
"position": [
-288,
16
],
"parameters": {
"jsCode": "// Extract objection clips from WayinVideo Find Moments results\nconst clips = $('4. WayinVideo \u2014 Get Moments Results').item.json.data?.clips || [];\n\nif (clips.length === 0) {\n throw new Error('No objection moments found in this recording. Try a different search query or check if the recording has audio.');\n}\n\n// Get form data\nconst repName = $('1. Form \u2014 Call Recording + Details').item.json['Sales Rep Name'];\nconst contactName = $('1. Form \u2014 Call Recording + Details').item.json['Contact Name'];\nconst companyName = $('1. Form \u2014 Call Recording + Details').item.json['Company Name'];\nconst callPurpose = $('1. Form \u2014 Call Recording + Details').item.json['Call Purpose'];\nconst callDate = $('1. Form \u2014 Call Recording + Details').item.json['Call Date'];\nconst objectionType = $('1. Form \u2014 Call Recording + Details').item.json['Objection Type to Search'];\nconst recordingUrl = $('1. Form \u2014 Call Recording + Details').item.json['Call Recording URL'];\n\n// Split into individual items \u2014 one per objection moment\nreturn clips.map((clip, index) => ({\n json: {\n clipIndex: index + 1,\n clipTitle: clip.title || `Objection Moment ${index + 1}`,\n clipDescription: clip.desc || '',\n clipScore: clip.score || 0,\n clipTags: Array.isArray(clip.tags) ? clip.tags.join(', ') : '',\n beginMs: clip.begin_ms || 0,\n endMs: clip.end_ms || 0,\n timestampStart: `${Math.floor((clip.begin_ms || 0) / 60000)}:${String(Math.floor(((clip.begin_ms || 0) % 60000) / 1000)).padStart(2, '0')}`,\n timestampEnd: `${Math.floor((clip.end_ms || 0) / 60000)}:${String(Math.floor(((clip.end_ms || 0) % 60000) / 1000)).padStart(2, '00')}`,\n repName,\n contactName,\n companyName,\n callPurpose,\n callDate,\n objectionType,\n recordingUrl\n }\n}));"
},
"typeVersion": 2
},
{
"id": "c5312a38-85cc-4226-b708-391e11aa0ebc",
"name": "8. AI Agent \u2014 Generate Coaching Note",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-96,
16
],
"parameters": {
"text": "=You are an expert sales coach analyzing a specific objection moment from a sales call recording.\n\nA video AI tool has already identified and extracted this exact moment from the recording. Your job is to analyze it and provide structured coaching for the sales manager.\n\n---\n\n## CALL CONTEXT\n- Sales Rep: {{ $json.repName }}\n- Contact: {{ $json.contactName }} at {{ $json.companyName }}\n- Call Type: {{ $json.callPurpose }}\n- Call Date: {{ $json.callDate }}\n- Objection Search Query Used: {{ $json.objectionType }}\n\n## MOMENT FOUND BY WAYINVIDEO\n- Moment Title: {{ $json.clipTitle }}\n- Description: {{ $json.clipDescription }}\n- Timestamp in Recording: {{ $json.timestampStart }} \u2014 {{ $json.timestampEnd }}\n- Relevance Score: {{ $json.clipScore }}/100\n- Tags: {{ $json.clipTags }}\n\n---\n\n## YOUR TASK\nBased on the moment description and context above, provide a structured coaching analysis.\n\n## RULES\n1. Base your analysis ONLY on the moment description provided \u2014 do not invent details\n2. Be specific and actionable \u2014 not generic sales advice\n3. Suggested reframe must be a specific alternative response the rep could use next time\n4. Keep each section concise \u2014 2-3 sentences maximum\n\n---\n\n## OUTPUT FORMAT\nReturn your response in this exact structure:\n\nOBJECTION_CATEGORY:\n[Choose one: Pricing / Competitor / Timing / Feature Gap / Trust / Authority / Need / Other]\n\nWHAT_HAPPENED:\n[1-2 sentences describing the objection based on the moment description]\n\nREP_RESPONSE_ASSESSMENT:\n[1-2 sentences assessing how the rep likely handled it based on context \u2014 or 'Unable to assess \u2014 review recording at timestamp']\n\nCOACHING_NOTE:\n[2-3 sentences of specific coaching advice for the sales manager to share with the rep]\n\nSUGGESTED_REFRAME:\n[Write the exact alternative response the rep could say next time this objection comes up \u2014 in first person, ready to use]",
"options": {},
"promptType": "define"
},
"typeVersion": 3.1
},
{
"id": "59ee5497-bf24-43e0-b9fe-79d08f31b76c",
"name": "9. OpenAI \u2014 GPT-4o-mini Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-96,
240
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {},
"builtInTools": {}
},
"typeVersion": 1.3
},
{
"id": "96def239-b9fd-4d85-ac2d-c2b90ee6e055",
"name": "10. Code \u2014 Parse Coaching Output",
"type": "n8n-nodes-base.code",
"position": [
304,
16
],
"parameters": {
"jsCode": "// Parse AI coaching output\nconst output = $input.first().json.output || '';\n\n// Extract each section\nconst categoryMatch = output.match(/OBJECTION_CATEGORY:\\s*([\\s\\S]*?)(?=\\nWHAT_HAPPENED:|$)/);\nconst whatHappenedMatch = output.match(/WHAT_HAPPENED:\\s*([\\s\\S]*?)(?=\\nREP_RESPONSE_ASSESSMENT:|$)/);\nconst repResponseMatch = output.match(/REP_RESPONSE_ASSESSMENT:\\s*([\\s\\S]*?)(?=\\nCOACHING_NOTE:|$)/);\nconst coachingNoteMatch = output.match(/COACHING_NOTE:\\s*([\\s\\S]*?)(?=\\nSUGGESTED_REFRAME:|$)/);\nconst reframeMatch = output.match(/SUGGESTED_REFRAME:\\s*([\\s\\S]*)$/);\n\nconst objectionCategory = categoryMatch ? categoryMatch[1].trim() : 'Other';\nconst whatHappened = whatHappenedMatch ? whatHappenedMatch[1].trim() : '';\nconst repResponseAssessment = repResponseMatch ? repResponseMatch[1].trim() : '';\nconst coachingNote = coachingNoteMatch ? coachingNoteMatch[1].trim() : '';\nconst suggestedReframe = reframeMatch ? reframeMatch[1].trim() : '';\n\n// Get clip data from previous node\nconst clipData = $('7. Code \u2014 Extract Objection Clips').item.json;\n\nreturn [{\n json: {\n // Airtable fields\n callDate: clipData.callDate,\n repName: clipData.repName,\n contactName: clipData.contactName,\n companyName: clipData.companyName,\n callPurpose: clipData.callPurpose,\n objectionType: clipData.objectionType,\n objectionCategory,\n momentTitle: clipData.clipTitle,\n objectionDescription: `${whatHappened}\\n\\n${clipData.clipDescription}`,\n timestamp: `${clipData.timestampStart} \u2014 ${clipData.timestampEnd}`,\n relevanceScore: clipData.clipScore,\n repResponseAssessment,\n coachingNote,\n suggestedReframe,\n recordingUrl: clipData.recordingUrl,\n clipIndex: clipData.clipIndex\n }\n}];"
},
"typeVersion": 2
},
{
"id": "e7a4b334-758a-4e57-aafd-15fcfc180fdb",
"name": "11. HTTP \u2014 Save to Airtable",
"type": "n8n-nodes-base.httpRequest",
"position": [
544,
16
],
"parameters": {
"url": "=https://api.airtable.com/v0/YOUR_AIRTABLE_BASE_ID/YOUR_AIRTABLE_TABLE_NAME",
"method": "POST",
"options": {},
"jsonBody": "={\n \"fields\": {\n \"Call Date\": \"{{ $json.callDate }}\",\n \"Rep Name\": \"{{ $json.repName }}\",\n \"Contact Name\": \"{{ $json.contactName }}\",\n \"Company\": \"{{ $json.companyName }}\",\n \"Call Purpose\": \"{{ $json.callPurpose }}\",\n \"Objection Type\": \"{{ $json.objectionType }}\",\n \"Objection Category\": \"{{ $json.objectionCategory }}\",\n \"Moment Title\": \"{{ $json.momentTitle }}\",\n \"Objection Description\": \"{{ $json.objectionDescription }}\",\n \"Timestamp in Recording\": \"{{ $json.timestamp }}\",\n \"Relevance Score\": {{ $json.relevanceScore }},\n \"Rep Response Assessment\": \"{{ $json.repResponseAssessment }}\",\n \"Coaching Note\": \"{{ $json.coachingNote }}\",\n \"Suggested Reframe\": \"{{ $json.suggestedReframe }}\",\n \"Recording URL\": \"{{ $json.recordingUrl }}\",\n \"Status\": \"Needs Review\"\n }\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_TOKEN_HERE"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
}
],
"connections": {
"3. Wait \u2014 90 Seconds": {
"main": [
[
{
"node": "4. WayinVideo \u2014 Get Moments Results",
"type": "main",
"index": 0
}
]
]
},
"6. Wait \u2014 30 Seconds Retry": {
"main": [
[
{
"node": "4. WayinVideo \u2014 Get Moments Results",
"type": "main",
"index": 0
}
]
]
},
"9. OpenAI \u2014 GPT-4o-mini Model": {
"ai_languageModel": [
[
{
"node": "8. AI Agent \u2014 Generate Coaching Note",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"5. IF \u2014 Find Moments Complete?": {
"main": [
[
{
"node": "7. Code \u2014 Extract Objection Clips",
"type": "main",
"index": 0
}
],
[
{
"node": "6. Wait \u2014 30 Seconds Retry",
"type": "main",
"index": 0
}
]
]
},
"10. Code \u2014 Parse Coaching Output": {
"main": [
[
{
"node": "11. HTTP \u2014 Save to Airtable",
"type": "main",
"index": 0
}
]
]
},
"7. Code \u2014 Extract Objection Clips": {
"main": [
[
{
"node": "8. AI Agent \u2014 Generate Coaching Note",
"type": "main",
"index": 0
}
]
]
},
"1. Form \u2014 Call Recording + Details": {
"main": [
[
{
"node": "2. WayinVideo \u2014 Submit Find Moments",
"type": "main",
"index": 0
}
]
]
},
"2. WayinVideo \u2014 Submit Find Moments": {
"main": [
[
{
"node": "3. Wait \u2014 90 Seconds",
"type": "main",
"index": 0
}
]
]
},
"4. WayinVideo \u2014 Get Moments Results": {
"main": [
[
{
"node": "5. IF \u2014 Find Moments Complete?",
"type": "main",
"index": 0
}
]
]
},
"8. AI Agent \u2014 Generate Coaching Note": {
"main": [
[
{
"node": "10. Code \u2014 Parse Coaching Output",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Submit a sales call recording URL and describe the objection type you want to find — such as "pricing too expensive" or "competitor comparison" — and the workflow scans the recording automatically. WayinVideo's Find Moments API locates up to 5 exact moments in the recording that…
Source: https://n8n.io/workflows/15422/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
🎯 Create viral TikToks, Shorts, Reels, podcasts, and ASMR videos in minutes — all on autopilot.
Digistars - Scrape & Crawl. Uses httpRequest, n8n-nodes-firecrawl-scraper, googleSheets, lmChatOpenAi. Event-driven trigger; 63 nodes.
PixelSensei(ZH). Uses agent, outputParserStructured, formTrigger, lmChatOpenAi. Event-driven trigger; 55 nodes.
🧠 Automate end-to-end SEO blog creation and WordPress publishing using a GPT-5 multi-agent workflow with real-time research, metadata generation, and optional featured images.
Lection 9 main. Uses formTrigger, chatTrigger, agent, lmChatOpenAi. Event-driven trigger; 55 nodes.