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": "Rodopi Dent - Patients Upsert",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "patients-upsert",
"responseMode": "responseNode",
"options": {
"allowedOrigins": "*"
}
},
"id": "webhook",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
0
]
},
{
"parameters": {
"jsCode": "// Validate input\nconst body = $input.first().json.body;\n\nif (!body.name || !body.phone) {\n return [{\n json: {\n valid: false,\n error: '\u041b\u0438\u043f\u0441\u0432\u0430 \u0438\u043c\u0435 \u0438\u043b\u0438 \u0442\u0435\u043b\u0435\u0444\u043e\u043d'\n }\n }];\n}\n\n// Normalize phone to international +359 format\n// This prevents Google Sheets from stripping the leading 0\nlet phone = String(body.phone).replace(/[\\s()\\-.]/g, '');\nif (phone.startsWith('00359')) phone = '+359' + phone.slice(5);\nelse if (phone.startsWith('0') && phone.length >= 10) phone = '+359' + phone.slice(1);\nelse if (/^[89]\\d{8}$/.test(phone)) phone = '+359' + phone;\nelse if (!phone.startsWith('+') && phone.length >= 9) phone = '+359' + phone;\n\nreturn [{\n json: {\n valid: true,\n name: body.name.trim(),\n phone: phone\n }\n}];"
},
"id": "validate",
"name": "Validate Input",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
220,
0
]
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.valid }}",
"value2": true
}
]
}
},
"id": "is-valid",
"name": "Is Valid?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
440,
0
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: false, error: $json.error }) }}",
"options": {
"responseHeaders": {
"entries": [
{
"name": "Access-Control-Allow-Origin",
"value": "*"
}
]
}
}
},
"id": "respond-invalid",
"name": "Respond Invalid",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
660,
-100
]
},
{
"parameters": {
"operation": "read",
"documentId": {
"__rl": true,
"mode": "id",
"value": "1hv4XAfHhScA40Bm1kQ3I-Ih4SJuCBpOJxTOYDNb167g"
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Patients"
},
"options": {
"returnAllMatches": true
}
},
"id": "read-patients",
"name": "Read Patients",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
660,
100
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Check if patient with this phone already exists\nconst inputData = $('Validate Input').first().json;\nconst existingPatients = $('Read Patients').all().map(item => item.json);\n\n// Extract core 9-digit number for robust comparison across formats\n// +359887493315 / 0887493315 / 887493315 all map to 887493315\nconst phoneKey = (p) => String(p || '').replace(/[\\s()\\-.+]/g, '').replace(/^359/, '').replace(/^0/, '');\n\nconst inputKey = phoneKey(inputData.phone);\nconst phoneExists = existingPatients.some(patient => phoneKey(patient.phone) === inputKey);\n\n// Also check by name (case-insensitive)\nconst nameExists = existingPatients.some(patient =>\n (patient.name || '').toLowerCase().trim() === (inputData.name || '').toLowerCase().trim()\n);\n\nreturn [{\n json: {\n exists: phoneExists || nameExists,\n name: inputData.name,\n phone: inputData.phone\n }\n}];"
},
"id": "check-exists",
"name": "Check If Exists",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
880,
100
]
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.exists }}",
"value2": true
}
]
}
},
"id": "already-exists",
"name": "Already Exists?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1100,
100
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: true, message: '\u041f\u0430\u0446\u0438\u0435\u043d\u0442\u044a\u0442 \u0432\u0435\u0447\u0435 \u0441\u044a\u0449\u0435\u0441\u0442\u0432\u0443\u0432\u0430', action: 'skipped', name: $json.name, phone: $json.phone }) }}",
"options": {
"responseHeaders": {
"entries": [
{
"name": "Access-Control-Allow-Origin",
"value": "*"
}
]
}
}
},
"id": "respond-exists",
"name": "Respond Already Exists",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1320,
0
]
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"mode": "id",
"value": "1hv4XAfHhScA40Bm1kQ3I-Ih4SJuCBpOJxTOYDNb167g"
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Patients"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"name": "={{ $json.name }}",
"phone": "={{ \"'\" + $json.phone }}",
"createdAt": "={{ new Date().toISOString().split('T')[0] }}"
}
},
"options": {}
},
"id": "append-patient",
"name": "Append New Patient",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
1320,
200
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: true, message: '\u041f\u0430\u0446\u0438\u0435\u043d\u0442\u044a\u0442 \u0435 \u0434\u043e\u0431\u0430\u0432\u0435\u043d', action: 'created', name: $('Check If Exists').first().json.name, phone: $('Check If Exists').first().json.phone }) }}",
"options": {
"responseHeaders": {
"entries": [
{
"name": "Access-Control-Allow-Origin",
"value": "*"
}
]
}
}
},
"id": "respond-created",
"name": "Respond Created",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1540,
200
]
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "Validate Input",
"type": "main",
"index": 0
}
]
]
},
"Validate Input": {
"main": [
[
{
"node": "Is Valid?",
"type": "main",
"index": 0
}
]
]
},
"Is Valid?": {
"main": [
[
{
"node": "Respond Invalid",
"type": "main",
"index": 0
}
],
[
{
"node": "Read Patients",
"type": "main",
"index": 0
}
]
]
},
"Read Patients": {
"main": [
[
{
"node": "Check If Exists",
"type": "main",
"index": 0
}
]
]
},
"Check If Exists": {
"main": [
[
{
"node": "Already Exists?",
"type": "main",
"index": 0
}
]
]
},
"Already Exists?": {
"main": [
[
{
"node": "Respond Already Exists",
"type": "main",
"index": 0
}
],
[
{
"node": "Append New Patient",
"type": "main",
"index": 0
}
]
]
},
"Append New Patient": {
"main": [
[
{
"node": "Respond Created",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"tags": [
{
"name": "Rodopi Dent"
}
]
}
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.
googleSheetsOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Rodopi Dent - Patients Upsert. Uses googleSheets. Webhook trigger; 10 nodes.
Source: https://github.com/Georgi-Piskov/RODOPI-DENT/blob/f071a84326ea5adf54e4eb20a2ba3e34aac5b728/n8n-workflows/22-patients-upsert.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.
[SANTOBET] FLUXO TODO - BACKUP. Uses googleSheets, httpRequest, googleSheetsTrigger. Webhook trigger; 57 nodes.
FLUXO DISPARO DATA E HORA. Uses itemLists, googleSheets, httpRequest. Webhook trigger; 48 nodes.
This workflow allows you to accept online payments via YooKassa and log both orders and transactions in Google Sheets — all without writing a single line of code. It supports full payment flow: produc
Transform your n8n instance management with this advanced automation system featuring artificial intelligence-driven workflow selection. This template provides comprehensive maintenance operations wit
Nexus_v6(ล่าสุดจริงๆ)ล่าสุดไกไก. Uses googleSheets, httpRequest. Webhook trigger; 41 nodes.