{
  "id": "UfGSlAVdkgxtNcTm",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "STT & TTS in whatsapp",
  "tags": [],
  "nodes": [
    {
      "id": "34e28c9e-c3f5-4feb-b3f7-714d15c0f3d3",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        1680
      ],
      "parameters": {
        "width": 480,
        "height": 912,
        "content": "## STT & TTS in whatsapp\n\n### How it works\n\n1. The workflow triggers upon receiving a message or media via WhatsApp and extracts relevant parameters.\n2. It routes the payload based on whether the input is a video or text message.\n3. Videos are downloaded, uploaded to FreeConvert, converted to MP3, and checked periodically for completion.\n4. The resulting audio is transcribed using Google Gemini and sent back to the user on WhatsApp.\n5. Alternatively, text messages trigger a sub-workflow to generate speech audio and send it back via WhatsApp.\n\n### Setup steps\n\n- Configure WhatsApp API credentials for triggers and messaging nodes\n- Set up FreeConvert API credentials for video-to-audio conversion\n- Configure Google Gemini API credentials for audio transcription\n- Set up Fish Audio credentials if utilizing text-to-speech sub-workflows\n\n### Customization\n\nEnsure all API keys (WhatsApp, FreeConvert, Google Gemini, and Fish Audio) are properly configured in n8n credentials.\n\n@[youtube](GFD8sBwHheg)"
      },
      "typeVersion": 1
    },
    {
      "id": "263cb5c5-4ebf-402b-b3f0-b8f615596c09",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1472,
        1760
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Receive and extract WhatsApp message\n\nReceives incoming messages from WhatsApp and extracts message parameters like phone, type, media ID, and body text."
      },
      "typeVersion": 1
    },
    {
      "id": "2dfeaeec-ead5-4f80-b8ad-ef974095ea0d",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1920,
        1760
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 400,
        "content": "## Determine media type route\n\nEvaluates whether the incoming WhatsApp media is a video file or text/audio message."
      },
      "typeVersion": 1
    },
    {
      "id": "00e7ed87-7f43-433e-8118-563fcde63193",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2368,
        1760
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Download video and create job\n\nDownloads the video file from WhatsApp and initializes a conversion job with FreeConvert."
      },
      "typeVersion": 1
    },
    {
      "id": "8f214288-f882-4f16-aa64-b03b4bffd95d",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2816,
        1760
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Prepare and upload video file\n\nPrepares binary data and uploads the video file to the conversion service."
      },
      "typeVersion": 1
    },
    {
      "id": "a3549ed3-c3f1-4358-9ccf-16e664026d0b",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3264,
        1760
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 432,
        "content": "## Monitor conversion job status\n\nWaits and repeatedly checks the conversion job status until processing is complete."
      },
      "typeVersion": 1
    },
    {
      "id": "45e25a0b-248b-4997-b2aa-bfb53c025a35",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3936,
        1760
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Download MP3 and process data\n\nDownloads the converted MP3 audio and prepares it for transcription."
      },
      "typeVersion": 1
    },
    {
      "id": "edb361de-fab1-4810-b1b5-8529d773a01e",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4384,
        1760
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Transcribe audio and send response\n\nTranscribes the audio recording using Google Gemini and sends the result back to the user via WhatsApp."
      },
      "typeVersion": 1
    },
    {
      "id": "ed26f8d7-9ebc-4e19-b4b3-a2ca386a0a60",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2384,
        2096
      ],
      "parameters": {
        "color": 7,
        "height": 352,
        "content": "## Generate voice note from text\n\nInvokes a sub-workflow to generate voice notes from text messages."
      },
      "typeVersion": 1
    },
    {
      "id": "e464b359-3bde-43d3-a493-34c20a7c5f49",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1472,
        2288
      ],
      "parameters": {
        "color": 7,
        "width": 864,
        "height": 304,
        "content": "## Generate speech and send media\n\nHandles sub-workflow execution triggered by the main flow to convert text into speech and upload media back to WhatsApp."
      },
      "typeVersion": 1
    },
    {
      "id": "080c0baa-ecb1-479e-99b2-8cebbc13d05b",
      "name": "When WhatsApp Message Received",
      "type": "n8n-nodes-base.whatsAppTrigger",
      "position": [
        1520,
        1920
      ],
      "parameters": {
        "options": {
          "messageStatusUpdates": [
            "sent"
          ]
        },
        "updates": [
          "messages"
        ]
      },
      "credentials": {
        "whatsAppTriggerApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2afe6692-d0ca-499d-b815-0f99a675ca69",
      "name": "Set Message Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        1744,
        1920
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "phone",
              "type": "string",
              "value": "={{ $json.messages[0].from }}"
            },
            {
              "id": "a2",
              "name": "type",
              "type": "string",
              "value": "={{ $json.messages[0].type }} {{ $json.messages[0].document.mime_type }}"
            },
            {
              "id": "a3",
              "name": "media_id",
              "type": "string",
              "value": "={{ $json.messages[0].document.url }}"
            },
            {
              "id": "a4",
              "name": "text_body",
              "type": "string",
              "value": "={{ $json.messages[0].text?.body || '' }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "fbe6d57b-82c2-4410-a026-5c68b038c302",
      "name": "Execute Voice Note Workflow",
      "type": "n8n-nodes-base.executeWorkflow",
      "position": [
        2432,
        2288
      ],
      "parameters": {
        "mode": "each",
        "options": {},
        "workflowId": {
          "__rl": true,
          "mode": "list",
          "value": "UfGSlAVdkgxtNcTm",
          "cachedResultUrl": "/workflow/UfGSlAVdkgxtNcTm",
          "cachedResultName": "STT & TTS in whatsapp"
        },
        "workflowInputs": {
          "value": {
            "phone": "={{ $json.phone }}",
            "text_body": "={{ $json.text_body.replace('/tts ', '') }}"
          },
          "schema": [
            {
              "id": "phone",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "text_body",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "text_body",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "80151d6e-b455-4aeb-b899-2929472abcc3",
      "name": "When Called by Main Workflow",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "position": [
        1520,
        2416
      ],
      "parameters": {
        "workflowInputs": {
          "values": [
            {
              "name": "phone"
            },
            {
              "name": "text_body"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "ae9ea0c7-ee48-47c3-a0e3-7565b868172d",
      "name": "Post Conversion Job",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2640,
        1888
      ],
      "parameters": {
        "url": "https://api.freeconvert.com/v1/process/jobs",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"tasks\": {\n    \"import-1\": { \"operation\": \"import/upload\" },\n    \"convert-1\": { \"operation\": \"convert\", \"input\": \"import-1\", \"output_format\": \"mp3\" },\n    \"export-1\": { \"operation\": \"export/url\", \"input\": \"convert-1\" }\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
    },
    {
      "id": "8e2c8f2c-c1ef-4469-9ad7-ee8601b235b8",
      "name": "Upload File for Conversion",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3088,
        1888
      ],
      "parameters": {
        "url": "={{ $json.tasks.find(t => t.name === 'import-1').result.form.url }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "name": "signature",
              "value": "={{ $json.tasks.find(t => t.name === 'import-1').result.form.parameters.signature }}"
            },
            {
              "name": "file",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "data"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "143acf14-3f31-45c0-ae5f-e9432e89433e",
      "name": "Wait for Conversion Status",
      "type": "n8n-nodes-base.wait",
      "position": [
        3312,
        1968
      ],
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "f43f7242-4210-4c1b-9d76-120cdd218522",
      "name": "Fetch Conversion Job Status",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3536,
        1888
      ],
      "parameters": {
        "url": "=https://api.freeconvert.com/v1/process/jobs/{{ $('Post Conversion Job').item.json.id }}",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "c3d72513-0fd3-440a-85bb-c183b28c00e4",
      "name": "If Conversion Complete",
      "type": "n8n-nodes-base.if",
      "position": [
        3760,
        1968
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond1",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.status }}",
              "rightValue": "completed"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "c5bc0bfa-07ac-441d-a4c6-2ad006ae603e",
      "name": "Fetch Converted MP3",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3984,
        1888
      ],
      "parameters": {
        "url": "={{ $json.tasks.find(t => t.name === 'export-1').result.url }}",
        "options": {
          "response": {
            "response": {}
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "71413998-7483-4317-b5eb-b7cbde6285e8",
      "name": "Generate Speech with FishAudio",
      "type": "n8n-nodes-fishaudio.fishaudio",
      "position": [
        1744,
        2416
      ],
      "parameters": {
        "text": "={{ $('When Called by Main Workflow').item.json.text_body }}",
        "model": "=s2.1-pro-free",
        "voiceId": {
          "__rl": true,
          "mode": "id",
          "value": "92b9f644244f4571ba58d696a6ef5e67"
        },
        "additionalOptions": {
          "speed": 1
        }
      },
      "credentials": {
        "fishaudioApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "bdb359ed-0b72-4798-a8ba-1955c84da0b8",
      "name": "Upload media",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        1968,
        2416
      ],
      "parameters": {
        "resource": "media",
        "phoneNumberId": "1083481144853090",
        "additionalFields": {}
      },
      "credentials": {
        "whatsAppApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "b23075e4-fdf5-4bc7-9454-b4938230c543",
      "name": "Send Voice Note Confirmation",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        2192,
        2416
      ],
      "parameters": {
        "mediaId": "={{ $json.id }}",
        "mediaPath": "useMediaId",
        "operation": "send",
        "messageType": "audio",
        "phoneNumberId": "1083481144853090",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $('When Called by Main Workflow').item.json.phone }}"
      },
      "credentials": {
        "whatsAppApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "25401f60-e48a-4d24-a1bc-1011ecb42429",
      "name": "If Text Conversion Requested",
      "type": "n8n-nodes-base.if",
      "position": [
        2192,
        1984
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "17ee0ff6-86ce-4843-b7e8-133f9756583b",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "leftValue": "={{ $json.text_body }}",
              "rightValue": "/tts"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "a822c28f-83c9-44f1-a053-e3ed8c0e4cb5",
      "name": "Prepare File Upload",
      "type": "n8n-nodes-base.code",
      "position": [
        2864,
        1888
      ],
      "parameters": {
        "jsCode": "// Code node: \"Prepare Upload\"\n// Re-attaches the original OGG binary (from Download Voice Sample)\n// to the current item (Create Job's JSON response), and validates the job response.\n\nconst jobResponse = $input.item.json;\n\nif (!jobResponse.tasks || !Array.isArray(jobResponse.tasks)) {\n  throw new Error('Create Job response missing \"tasks\" array \u2014 check FreeConvert API response for errors: ' + JSON.stringify(jobResponse));\n}\n\nconst importTask = jobResponse.tasks.find(t => t.name === 'import-1');\n\nif (!importTask || !importTask.result || !importTask.result.form) {\n  throw new Error('Could not find import-1 task with a valid upload form in the job response: ' + JSON.stringify(jobResponse));\n}\n\nconst originalBinary = $('Fetch Incoming Video').item.binary;\n\nif (!originalBinary || !originalBinary.data) {\n  throw new Error('No binary data found on \"Download Voice Sample\" item \u2014 check the binary property name.');\n}\n\nreturn {\n  json: jobResponse,\n  binary: originalBinary\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "1a9cf410-1c94-42bf-84bb-f1d3555a2219",
      "name": "Transcribe Audio with Gemini",
      "type": "@n8n/n8n-nodes-langchain.googleGemini",
      "position": [
        4432,
        1920
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "models/gemini-3.1-flash-lite",
          "cachedResultName": "models/gemini-3.1-flash-lite"
        },
        "options": {},
        "resource": "audio",
        "simplify": false,
        "inputType": "binary"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "3256eb0c-1096-482c-b3e9-25829800042b",
      "name": "Process Audio Metadata",
      "type": "n8n-nodes-base.code",
      "position": [
        4208,
        1888
      ],
      "parameters": {
        "jsCode": "for (const item of $input.all()) {\n  // Check if binary data exists (adjust 'data' if your binary property has a different name)\n  if (item.binary && item.binary.data) {\n    item.binary.data.mimeType = \"audio/mpeg\";\n  }\n}\n\nreturn $input.all();"
      },
      "typeVersion": 2
    },
    {
      "id": "f5a08e99-7884-442d-b6b6-ad09503c59a6",
      "name": "Send Transcription to WhatsApp",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        4656,
        1920
      ],
      "parameters": {
        "textBody": "={{ $json.candidates[0].content.parts[0].text }}",
        "operation": "send",
        "phoneNumberId": "1083481144853090",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $('When WhatsApp Message Received').item.json.contacts[0].wa_id }}"
      },
      "credentials": {
        "whatsAppApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "cc712203-846d-4d33-9c21-976d5c3aac5f",
      "name": "If Message Type Video",
      "type": "n8n-nodes-base.if",
      "position": [
        1968,
        1888
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "7cb4a67d-77dc-49cc-82ba-b010bfd22516",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "leftValue": "={{ $json.type }}",
              "rightValue": "video"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "c8ce1cdd-3387-402f-bae1-ba91f6a530f2",
      "name": "Fetch Incoming Video",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2416,
        1888
      ],
      "parameters": {
        "url": "={{ $json.media_id }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "whatsAppApi"
      },
      "credentials": {
        "whatsAppApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "3195080d-b041-46c5-975c-e71695ec372f",
  "nodeGroups": [],
  "connections": {
    "Upload media": {
      "main": [
        [
          {
            "node": "Send Voice Note Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Message Fields": {
      "main": [
        [
          {
            "node": "If Message Type Video",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Converted MP3": {
      "main": [
        [
          {
            "node": "Process Audio Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post Conversion Job": {
      "main": [
        [
          {
            "node": "Prepare File Upload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare File Upload": {
      "main": [
        [
          {
            "node": "Upload File for Conversion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Incoming Video": {
      "main": [
        [
          {
            "node": "Post Conversion Job",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Message Type Video": {
      "main": [
        [
          {
            "node": "Fetch Incoming Video",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "If Text Conversion Requested",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Conversion Complete": {
      "main": [
        [
          {
            "node": "Fetch Converted MP3",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait for Conversion Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Audio Metadata": {
      "main": [
        [
          {
            "node": "Transcribe Audio with Gemini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload File for Conversion": {
      "main": [
        [
          {
            "node": "Wait for Conversion Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Conversion Status": {
      "main": [
        [
          {
            "node": "Fetch Conversion Job Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Conversion Job Status": {
      "main": [
        [
          {
            "node": "If Conversion Complete",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Text Conversion Requested": {
      "main": [
        [
          {
            "node": "Execute Voice Note Workflow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transcribe Audio with Gemini": {
      "main": [
        [
          {
            "node": "Send Transcription to WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Called by Main Workflow": {
      "main": [
        [
          {
            "node": "Generate Speech with FishAudio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Speech with FishAudio": {
      "main": [
        [
          {
            "node": "Upload media",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When WhatsApp Message Received": {
      "main": [
        [
          {
            "node": "Set Message Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}