This workflow follows the Google Sheets → 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 →
{
"name": "MediFlow \u2014 Prise de Rendez-vous WhatsApp (Evolution API)",
"active": false,
"settings": {
"executionOrder": "v1"
},
"nodes": [
{
"id": "3cca0317-e1d3-4a91-a119-88e5c259097b",
"name": "\ud83d\udce5 Webhook Evolution API",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
-4576,
2368
],
"parameters": {
"httpMethod": "POST",
"path": "mediflow-rdv",
"responseMode": "responseNode",
"options": {}
}
},
{
"id": "f5332879-f396-493f-9089-2fab2098528c",
"name": "\ud83d\udd0d Extraire Message & T\u00e9l\u00e9phone",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-4352,
2368
],
"parameters": {
"jsCode": "// Parse le payload Evolution API et extrait le num\u00e9ro + message\nconst body = $input.first().json.body;\nlet phone = '';\nlet message = '';\nlet messageType = 'text';\n\ntry {\n if (body.data) {\n phone = body.data.key?.remoteJid?.replace('@s.whatsapp.net', '') || '';\n message = body.data.message?.conversation ||\n body.data.message?.extendedTextMessage?.text || '';\n messageType = body.data.messageType || 'conversation';\n const isFromMe = body.data?.key?.fromMe || false;\n if (isFromMe) return [{ json: { skip: true, reason: 'message_sortant' } }];\n } else if (body.messages) {\n phone = body.messages[0]?.key?.remoteJid?.replace('@s.whatsapp.net', '') || '';\n message = body.messages[0]?.message?.conversation || '';\n if (body.messages[0]?.key?.fromMe) return [{ json: { skip: true, reason: 'message_sortant' } }];\n }\n} catch(e) {\n console.log('Parse error:', e.message);\n}\n\n// Ignorer les messages non-texte (audio, images)\nif (!message || messageType === 'audioMessage' || messageType === 'imageMessage') {\n return [{ json: { skip: true, reason: 'pas_texte', phone } }];\n}\n\nreturn [{ json: { phone, message, messageType, timestamp: new Date().toISOString() } }];"
}
},
{
"id": "f174cd85-9803-40c0-814f-b1040486cfbe",
"name": "\ud83d\udea6 Message valide ?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
-4128,
2368
],
"parameters": {
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.skip }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "notEquals"
}
}
]
}
}
},
{
"id": "26eb3dfc-726a-4e1b-a1f2-7e3d273ea682",
"name": "\ud83d\udc64 Chercher Patient (Google Sheets)",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
-3728,
2176
],
"alwaysOutputData": true,
"parameters": {
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "PATIENTS",
"mode": "name"
},
"filtersUI": {
"values": [
{
"lookupColumn": "telephone",
"lookupValue": "={{ $('\ud83d\udd0d Extraire Message & T\u00e9l\u00e9phone').item.json.phone }}"
}
]
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"id": "6358dbec-c150-4149-bb0a-1e140f292976",
"name": "\ud83c\udd95 Patient connu ?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
-3520,
2176
],
"parameters": {
"conditions": {
"conditions": [
{
"leftValue": "={{ $('\ud83d\udc64 Chercher Patient (Google Sheets)').item.json.patient_id }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty"
}
}
]
}
}
},
{
"id": "d4126e7b-9824-4ae4-a107-f17de62095b2",
"name": "\u2795 Cr\u00e9er Nouveau Patient",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
-3376,
2592
],
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "PATIENTS",
"mode": "name"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"telephone": "={{ $('\ud83d\udd0d Extraire Message & T\u00e9l\u00e9phone').item.json.phone }}",
"patient_id": "=PAT_{{ $now.toFormat('yyyyMMddHHmmss') }}",
"date_inscription": "={{ $now.toISO() }}"
}
}
}
},
{
"id": "6b13162a-897f-4fe1-b7b9-734f8e4b7413",
"name": "\ud83d\udc4b Envoyer Message Bienvenue",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-3168,
2624
],
"parameters": {
"method": "POST",
"url": "=https://YOUR_EVOLUTION_API_URL/message/sendText/YOUR_INSTANCE_NAME",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "number",
"value": "={{ $('\ud83d\udd0d Extraire Message & T\u00e9l\u00e9phone').item.json.phone }}"
},
{
"name": "text",
"value": "Bonjour et bienvenue chez MediFlow ! \ud83d\udc4b\n\nJe suis MediBot, votre assistant pour la prise de rendez-vous m\u00e9dicaux.\n\nComment puis-je vous aider aujourd'hui ? Vous pouvez me dire par exemple :\n- \"Je veux prendre un rendez-vous\"\n- \"Je veux annuler mon rendez-vous\"\n- \"J'ai une urgence m\u00e9dicale\""
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"id": "5449abe4-aa9a-48ce-91ed-ef2e811f5401",
"name": "\ud83d\udccb Lire \u00c9tat Conversation",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-3296,
2176
],
"parameters": {
"jsCode": "// R\u00e9cup\u00e8re l'\u00e9tat de conversation persist\u00e9 en Google Sheets\nconst patientData = $('\ud83d\udc64 Chercher Patient (Google Sheets)').item.json;\nconst messageData = $('\ud83d\udd0d Extraire Message & T\u00e9l\u00e9phone').item.json;\n\nconst etatConv = patientData.etat_conversation || 'LIBRE';\nconst creneauxProposes = patientData.creneaux_proposes || '';\n\nreturn [{\n json: {\n phone: messageData.phone,\n message: messageData.message,\n patient_id: patientData.patient_id || '',\n patient_prenom: patientData.prenom || '',\n etat_conversation: etatConv,\n creneaux_proposes: creneauxProposes\n }\n}];"
}
},
{
"id": "cf75af23-5df4-4204-b086-ec6c1d4acc28",
"name": "\u23f3 En attente de choix ?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
-3072,
2176
],
"parameters": {
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.etat_conversation }}",
"rightValue": "ATTENTE_CRENEAU",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
}
},
{
"id": "64c5a635-42d6-45d6-a51b-5cbd49937397",
"name": "Pr\u00e9pare Requ\u00eate Claude",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-2944,
2912
],
"parameters": {
"jsCode": "// Construit le prompt Claude selon l'\u00e9tat de conversation\nconst etat = $('\ud83d\udccb Lire \u00c9tat Conversation').item.json.etat_conversation || 'LIBRE';\nconst message = $('\ud83d\udccb Lire \u00c9tat Conversation').item.json.message || '';\n\nconst body = {\n model: \"claude-haiku-4-5-20251001\",\n max_tokens: 600,\n system: `Tu es MediBot, l'assistant de prise de rendez-vous d'une clinique priv\u00e9e.\n\nServices : cardiologie, p\u00e9diatrie, g\u00e9n\u00e9raliste, gyn\u00e9cologie, dermatologie.\n\u00c9tat conversation : ${etat}\n\nR\u00c8GLES :\n1. RDV sans sp\u00e9cialit\u00e9 \u2192 retourne PRENDRE_RDV, specialite=null, liste les sp\u00e9cialit\u00e9s dans message_reponse\n2. \u00c9tat ATTENTE_SPECIALITE \u2192 le message est la sp\u00e9cialit\u00e9 choisie\n3. Sp\u00e9cialit\u00e9 pr\u00e9cis\u00e9e \u2192 retourne PRENDRE_RDV avec specialite renseign\u00e9e\n4. Urgence \u2192 retourne URGENCE\n5. Autre \u2192 retourne AUTRE avec r\u00e9ponse utile\n\nRetourne UNIQUEMENT un JSON valide :\n{\n \"intention\": \"PRENDRE_RDV\" | \"ANNULER_RDV\" | \"CONFIRMER_RDV\" | \"URGENCE\" | \"AUTRE\",\n \"specialite\": null | \"cardiologie\" | \"p\u00e9diatrie\" | \"g\u00e9n\u00e9raliste\" | \"gyn\u00e9cologie\" | \"dermatologie\",\n \"message_reponse\": \"message chaleureux en fran\u00e7ais\",\n \"demander_specialite\": false\n}`,\n messages: [{ role: \"user\", content: \"Message du patient : \" + message }]\n};\n\nreturn [{ json: { claudeBody: JSON.stringify(body) } }];"
}
},
{
"id": "177206be-e2e0-4625-997c-7481e8472dd2",
"name": "\ud83e\udd16 Claude \u2014 Analyser Intention",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-2704,
2912
],
"parameters": {
"method": "POST",
"url": "https://api.anthropic.com/v1/messages",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "anthropicApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "anthropic-version",
"value": "2023-06-01"
},
{
"name": "content-type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.parse($json.claudeBody) }}",
"options": {
"timeout": 30000
}
},
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
}
},
{
"id": "68b7a017-3d1e-46b1-9b5b-e9e90f99d208",
"name": "\u2699\ufe0f Parser R\u00e9ponse Claude",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-2496,
2912
],
"parameters": {
"jsCode": "// Parse la r\u00e9ponse JSON de Claude et attache les donn\u00e9es patient\nconst response = $input.first().json;\nlet claudeData = {};\n\ntry {\n const rawText = response.content[0].text;\n const cleaned = rawText.replace(/```json|```/g, '').trim();\n claudeData = JSON.parse(cleaned);\n} catch(e) {\n claudeData = {\n intention: 'AUTRE',\n message_reponse: 'Bonjour ! Je suis MediBot, votre assistant. Comment puis-je vous aider ?',\n demander_specialite: false\n };\n}\n\nclaudeData.phone = $('\ud83d\udccb Lire \u00c9tat Conversation').item.json.phone;\nclaudeData.patient_id = $('\ud83d\udccb Lire \u00c9tat Conversation').item.json.patient_id;\nclaudeData.patient_prenom = $('\ud83d\udccb Lire \u00c9tat Conversation').item.json.patient_prenom;\n\nreturn [{ json: claudeData }];"
}
},
{
"id": "89870dba-36e7-42d1-bfc7-ab861990eae7",
"name": "\ud83d\udd00 Router par Intention",
"type": "n8n-nodes-base.switch",
"typeVersion": 3,
"position": [
-2304,
2848
],
"parameters": {
"rules": {
"values": [
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.intention }}",
"rightValue": "URGENCE",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
},
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.intention }}",
"rightValue": "PRENDRE_RDV",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"leftValue": "={{ $json.specialite }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
]
}
},
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.intention }}",
"rightValue": "ANNULER_RDV",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
}
]
},
"options": {
"fallbackOutput": "extra"
}
}
},
{
"id": "0b7fc34d-a5b3-4769-b30c-a9d98b16dd79",
"name": "\ud83d\udcc5 Cr\u00e9er RDV Confirm\u00e9",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
-2016,
1024
],
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_ID",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "RENDEZ-VOUS",
"mode": "name"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"rdv_id": "=RDV_{{ $now.toFormat('yyyyMMddHHmmss') }}",
"patient_id": "={{ $json.patient_id }}",
"patient_telephone": "={{ $json.phone }}",
"medecin_nom": "={{ $json.creneau_choisi.medecin_nom }}",
"specialite": "={{ $json.creneau_choisi.specialite }}",
"date_rdv": "={{ $json.creneau_choisi.date }}",
"heure_rdv": "={{ $json.creneau_choisi.heure_debut }}",
"lieu": "={{ $json.creneau_choisi.lieu || 'Clinique MediFlow' }}",
"statut": "CONFIRME",
"date_creation": "={{ $now.toISO() }}"
}
}
}
},
{
"id": "51a92ed2-12b3-4bbb-883f-be37ead1c0e2",
"name": "\u2705 R\u00e9ponse Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
752,
2480
],
"parameters": {
"respondWith": "json",
"responseBody": "{ \"status\": \"ok\" }",
"options": {
"responseCode": 200
}
}
}
],
"connections": {
"\ud83d\udce5 Webhook Evolution API": {
"main": [
[
{
"node": "\ud83d\udd0d Extraire Message & T\u00e9l\u00e9phone",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd0d Extraire Message & T\u00e9l\u00e9phone": {
"main": [
[
{
"node": "\ud83d\udea6 Message valide ?",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udea6 Message valide ?": {
"main": [
[
{
"node": "\ud83d\udc64 Chercher Patient (Google Sheets)",
"type": "main",
"index": 0
}
],
[
{
"node": "\u2705 R\u00e9ponse Webhook",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udc64 Chercher Patient (Google Sheets)": {
"main": [
[
{
"node": "\ud83c\udd95 Patient connu ?",
"type": "main",
"index": 0
}
]
]
},
"\ud83c\udd95 Patient connu ?": {
"main": [
[
{
"node": "\ud83d\udccb Lire \u00c9tat Conversation",
"type": "main",
"index": 0
}
],
[
{
"node": "\u2795 Cr\u00e9er Nouveau Patient",
"type": "main",
"index": 0
}
]
]
},
"\u2795 Cr\u00e9er Nouveau Patient": {
"main": [
[
{
"node": "\ud83d\udc4b Envoyer Message Bienvenue",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udc4b Envoyer Message Bienvenue": {
"main": [
[
{
"node": "\u2705 R\u00e9ponse Webhook",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udccb Lire \u00c9tat Conversation": {
"main": [
[
{
"node": "\u23f3 En attente de choix ?",
"type": "main",
"index": 0
}
]
]
},
"\u23f3 En attente de choix ?": {
"main": [
[
{
"node": "\ud83e\udd16 Claude \u2014 Analyser Intention",
"type": "main",
"index": 0
}
],
[
{
"node": "Pr\u00e9pare Requ\u00eate Claude",
"type": "main",
"index": 0
}
]
]
},
"Pr\u00e9pare Requ\u00eate Claude": {
"main": [
[
{
"node": "\ud83e\udd16 Claude \u2014 Analyser Intention",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\udd16 Claude \u2014 Analyser Intention": {
"main": [
[
{
"node": "\u2699\ufe0f Parser R\u00e9ponse Claude",
"type": "main",
"index": 0
}
]
]
},
"\u2699\ufe0f Parser R\u00e9ponse Claude": {
"main": [
[
{
"node": "\ud83d\udd00 Router par Intention",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcc5 Cr\u00e9er RDV Confirm\u00e9": {
"main": [
[
{
"node": "\u2705 R\u00e9ponse Webhook",
"type": "main",
"index": 0
}
]
]
}
},
"tags": [
"mediflow",
"whatsapp",
"healthcare",
"claude-ai"
]
}
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.
anthropicApigoogleSheetsOAuth2ApihttpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
MediFlow — Prise de Rendez-vous WhatsApp (Evolution API). Uses googleSheets, httpRequest. Webhook trigger; 15 nodes.
Source: https://github.com/guilloulearnlife/guilloulearnlife/blob/faad082af35d536f7fb30b49ee497cbdd357b6eb/docs/workflows/mediflow-rdv-whatsapp.json — 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.
This workflow is a complete, production-ready solution for recovering abandoned carts in Shopify stores using a multi-channel, multi-touch approach. It automates personalized follow-ups via Email, SMS
01_order_processing_tilda. Uses stickyNote, googleSheets, httpRequest, telegram. Webhook trigger; 25 nodes.
Turn every sales meeting into a coaching opportunity. This workflow automatically analyzes tldv meeting recordings using OpenAI (GPT-4) to provide instant, actionable feedback to your sales team.
Automated video processing system that monitors S3 for new uploads, generates thumbnails and preview clips, extracts metadata, transcodes to multiple formats, and distributes to CDN with webhook notif
Eliminate manual data entry from your accounts payable process. This workflow transforms raw invoice scans into structured financial records by combining UploadToURL for hosting, AWS Textract for OCR