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": "Course Bot - Didar CRM (v6)",
"nodes": [
{
"id": "webhook",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
250,
500
],
"parameters": {
"httpMethod": "POST",
"path": "course-bot",
"responseMode": "lastNode",
"options": {}
}
},
{
"id": "config",
"name": "Config",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
500,
500
],
"parameters": {
"jsCode": "\nconst CONFIG = {\n WEBHOOK_SECRET: 'cbot_s3cr3t_hmac_2026',\n DIDAR_API_KEY: '1t31qjd4bl43cxej1yybhr2uf24ael2a',\n OWNERS: [\n {id: 'a52a4294-f773-48cf-8201-d4402f7b7780', name: '\u0639\u0628\u0627\u0633\u06cc', weight: 3},\n {id: 'f4d3c7ad-3fb4-42f3-bf78-04ca86a24a1f', name: '\u063a\u0644\u0627\u0645\u06cc', weight: 2},\n ],\n PIPELINE_ID: '9b0e5024-4822-4833-abe6-8ca426a937ae',\n COMPANY_ID: '00000000-0000-0000-0000-000000000000',\n ACTIVITY_TYPE_SALES: 'dc40fdb0-f8a5-4a40-80fe-1102a8a8b9f3',\n ACTIVITY_TYPE_FOLLOWUP: '3b9512e1-663c-4265-827e-43fa5bcee70e',\n STAGES: {\n register: 'b7e97097-ff9b-4207-a2e7-07dd2ea606af',\n lesson_1: 'ffa64a67-02e0-462b-a0c2-60c85eee6af5',\n lesson_2: 'bcfe1289-12bf-4ee6-88e7-2dcf0ed48469',\n lesson_3: '4223a51c-544b-41e2-94bc-1e99016fbaba',\n lesson_4: '09856491-6d78-40df-b3ed-c80083e77a8f',\n lesson_5: '0fcb3769-e1ad-45d5-8847-e51e97065d85',\n lesson_6: 'aee7d1c1-5f18-43e8-b1cb-050d59ce3517',\n lesson_7: 'd9fc8133-052f-465b-9289-4211272b6e18',\n lesson_8: '6faab0b4-be10-478d-9655-bf41eca744a8',\n sales_wait: '6faab0b4-be10-478d-9655-bf41eca744a8',\n followup_1: 'be5d2b7a-4cf6-4b5d-873f-a3a76ccf2310',\n followup_2: 'daa24b66-5151-4e57-b08e-032a2961c339',\n followup_3: '3d3cf5e9-0726-43db-9e01-885c50c3b2bd',\n won: ''\n },\n CUSTOM_FIELDS: {\n monthly_income: 'Field_996_0_26',\n staff_count: 'Field_996_0_25',\n job: 'J',\n best_call_time: 'Field_996_0_31',\n lead_score: 'Field_996_12_30',\n city: 'Field_996_0_11',\n income_class: ''\n }\n};\n\nconst body = $input.first().json.body || $input.first().json;\nconst event = body.event || {};\nconst user = body.user || {};\nconst course = body.course || {};\nconst lesson = body.lesson || {};\nconst progress = body.progress || {};\nconst payload = body.payload || {};\nconst eventType = event.type || '';\nconst eventAction = event.action || '';\nconst action = eventType + '.' + eventAction;\n// Dynamic CRM field mapping from bot\nconst botMapping = payload.crm_field_mapping || {};\nfor (const [key, val] of Object.entries(botMapping)) {\n if (val && !val.startsWith('person.') && val !== 'note') {\n CONFIG.CUSTOM_FIELDS[key] = val;\n }\n}\nconst phoneRaw = user.phone || (user.registration_data?.phone || user.registration_data?.mobile || '');\nconst phoneSearch = (function(p) { p = (p||'').replace(/[^0-9]/g, ''); return p.length >= 10 ? p.slice(-10) : p; })(phoneRaw);\nreturn [{json: {...body, CONFIG, action,\n user_phone: phoneRaw,\n phone_search: phoneSearch,\n user_name: (user.registration_data?.first_name||user.first_name||'') + ' ' + (user.registration_data?.last_name||user.last_name||''),\n course_title: course.title || '',\n lesson_title: lesson.title || '',\n lesson_order: lesson.lesson_number || lesson.order || 0,\n progress_percent: progress.percent || 0,\n progress_completed: progress.completed || 0,\n progress_total: progress.total || 0\n}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "router",
"name": "Router",
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
750,
500
],
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond_Register",
"leftValue": "={{ $json.action }}",
"rightValue": "lead.register",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Register"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond_Lesson",
"leftValue": "={{ $json.action }}",
"rightValue": "lesson.complete",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Lesson"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond_Form",
"leftValue": "={{ $json.action }}",
"rightValue": "form.submit",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Form"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond_QuizPass",
"leftValue": "={{ $json.action }}",
"rightValue": "quiz.pass",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "QuizPass"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond_QuizFail",
"leftValue": "={{ $json.action }}",
"rightValue": "quiz.fail",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "QuizFail"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond_Inactivity",
"leftValue": "={{ $json.action }}",
"rightValue": "inactivity.timeout",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Inactivity"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond_Complete",
"leftValue": "={{ $json.action }}",
"rightValue": "course.complete",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Complete"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond_Speed",
"leftValue": "={{ $json.action }}",
"rightValue": "speed.change",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Speed"
}
]
},
"options": {
"fallbackOutput": "extra"
}
}
},
{
"id": "prep_register",
"name": "Prep Register",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1050,
100
],
"parameters": {
"jsCode": "\nconst d = $input.first().json;\nconst C = d.CONFIG;\n\nconst owners = (C.OWNERS || []).filter(o => o.weight > 0);\nlet ownerId = owners.length ? owners[0].id : '';\nlet ownerName = owners.length ? owners[0].name : '';\n\nif (owners.length > 1) {\n let pool = [];\n for (const o of owners) {\n for (let i = 0; i < o.weight; i++) pool.push(o);\n }\n const selected = pool[Math.floor(Math.random() * pool.length)];\n ownerId = selected.id;\n ownerName = selected.name;\n}\n\nconst lastName = d.user?.registration_data?.last_name || d.user?.last_name || 'User';\nconst firstName = d.user?.registration_data?.first_name || d.user?.first_name || '';\nconst phone = d.user_phone || (d.user?.registration_data?.phone || d.user?.registration_data?.mobile || '');\nconst phoneSearch = d.phone_search || '';\nconst leadScore = d.payload?.lead_score || d.user?.lead_score || 0;\n\n// Use bot-provided lead_score_field_json if available, else build from CUSTOM_FIELDS\nlet leadScoreFieldJson = d.payload?.lead_score_field_json || '{}';\nif (leadScoreFieldJson === '{}' && C.CUSTOM_FIELDS.lead_score && C.CUSTOM_FIELDS.lead_score !== 'FIELD_GUID') {\n leadScoreFieldJson = JSON.stringify({[C.CUSTOM_FIELDS.lead_score]: String(leadScore)});\n}\n\nreturn [{json: {\n phone, phoneSearch, firstName, lastName,\n ownerId, ownerName, leadScore, leadScoreFieldJson,\n apiKey: C.DIDAR_API_KEY || '',\n pipelineId: C.PIPELINE_ID,\n stageId: C.STAGES.register,\n companyId: C.COMPANY_ID,\n activityTypeId: C.ACTIVITY_TYPE_SALES,\n courseTitle: d.course_title,\n dealTitle: '' + d.course_title + ' ' + firstName + ' ' + lastName,\n CONFIG: C\n}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "find_person",
"name": "Find Person",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
1300,
100
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "person",
"operation": "search",
"Keywords": "={{$json.phoneSearch}}",
"additionalFields": {
"Limit": 1
}
}
},
{
"id": "process_person",
"name": "Process Person",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1550,
100
],
"parameters": {
"jsCode": "\nconst prev = $('Prep Register').first().json;\nconst resp = $input.first().json;\nconst list = resp?.search_respons?.List || resp?.Response?.List || [];\nconst found = list.length > 0 ? list[0] : null;\nconst personId = found?.Id || null;\nreturn [{json: {...prev, personExists: !!personId, personId}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "if_person_exists",
"name": "IF Person Exists",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1800,
100
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json.personExists}}",
"value2": true
}
]
}
}
},
{
"id": "use_existing_person",
"name": "Use Existing Person",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
2050,
0
],
"parameters": {
"jsCode": "\nconst d = $input.first().json;\nreturn [{json: {...d}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "create_person",
"name": "Create Person",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
2050,
200
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "person",
"operation": "create",
"FirstName": "={{$json.firstName}}",
"LastName": "={{$json.lastName}}",
"MobilePhone": "={{$json.phone}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"additionalFields": {
"CompanyId": "={{$json.companyId}}",
"VisibilityType": "Owner",
"Fields": "={{$json.leadScoreFieldJson}}"
}
}
},
{
"id": "get_new_person_id",
"name": "Get New Person ID",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
2300,
200
],
"parameters": {
"jsCode": "\nconst prev = $('Process Person').first().json;\nconst resp = $input.first().json;\nconst newId = resp?.Response?.Id || '';\nreturn [{json: {...prev, personId: newId, personExists: false}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "unify_person",
"name": "Unify Person",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
2400,
100
],
"parameters": {
"jsCode": "const item = $input.first().json;\nif (!item.personId) throw new Error('No personId found after person search/create');\nreturn [{json: {...item}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "search_deal_reg",
"name": "Search Deal Reg V2",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2550,
100
],
"parameters": {
"method": "POST",
"url": "=https://app.didar.me/api/deal/search_v2?apikey={{ $json.apiKey }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({Criteria: {ContactIds: [$json.personId], PipelineId: $json.pipelineId}, From: 0, Limit: 50}) }}",
"options": {}
}
},
{
"id": "process_deal_reg",
"name": "Process Deal Reg",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
2800,
100
],
"parameters": {
"jsCode": "\nconst prev = $('Unify Person').first().json;\n\nconst resp = $input.first().json;\nconst deals = resp?.Response?.List || resp?.search_respons?.List || [];\nconst existingDeal = deals.find(d =>\n (d.PersonId === prev.personId || d.ContactId === prev.personId) && d.Status === 'Pending'\n);\n\nreturn [{json: {\n ...prev,\n dealExists: !!existingDeal,\n existingDealId: existingDeal?.Id || null\n}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "if_deal_exists",
"name": "IF Deal Exists",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
3050,
100
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json.dealExists}}",
"value2": true
}
]
}
}
},
{
"id": "skip_deal",
"name": "Skip Deal Create",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
3300,
0
],
"parameters": {
"jsCode": "\nconst d = $input.first().json;\nreturn [{json: {...d, dealId: d.existingDealId}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "create_deal",
"name": "Create Deal",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
3300,
200
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "deal",
"operation": "create",
"Title": "={{$json.dealTitle}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"PipelineMode": "manual",
"PipelineId": "={{$json.pipelineId}}",
"StageMode": "manual",
"PipelineStageId": "={{$json.stageId}}",
"PersonId": "={{$json.personId}}",
"CompanyId": "={{$json.companyId}}",
"Status": "Pending",
"LabelIds": [],
"additionalFields": {
"VisibilityType": "Owner"
}
}
},
{
"id": "after_create_deal",
"name": "After Create Deal",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
3550,
200
],
"parameters": {
"jsCode": "\nconst prev = $('Process Deal Reg').first().json;\nconst resp = $input.first().json;\nconst newDealId = resp?.Response?.Id || '';\nreturn [{json: {...prev, dealId: newDealId}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "unify_deal_id",
"name": "Unify Deal ID",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
3700,
100
],
"parameters": {
"jsCode": "const item = $input.first().json;\nif (!item.dealId) throw new Error('No dealId found after deal search/create');\nreturn [{json: {...item}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "happy_call",
"name": "Happy Call",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
3800,
100
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "activity",
"operation": "create",
"Title": "=\u062a\u0645\u0627\u0633 \u062e\u0648\u0634\u0627\u0645\u062f\u06af\u0648\u06cc\u06cc - {{$json.lastName}}",
"ActivityTypeMode": "manual",
"ActivityTypeIdManual": "={{$json.activityTypeId}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"IsDone": false,
"additionalFields": {
"Note": "=\u062b\u0628\u062a \u0646\u0627\u0645 \u062c\u062f\u06cc\u062f \u062f\u0631 \u062f\u0648\u0631\u0647 {{$json.courseTitle}}",
"ContactIds": "={{$json.personId}}",
"DealIds": "={{$json.dealId || $json.existingDealId || ''}}"
}
}
},
{
"id": "prep_response",
"name": "Prep Response",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
4050,
100
],
"parameters": {
"jsCode": "\nconst d = $input.first().json;\nconst prev = $('Unify Deal ID').first().json;\nconst ownerId = prev?.ownerId || d?.ownerId || '';\nconst ownerName = prev?.ownerName || d?.ownerName || '';\nreturn [{json: {status: 'ok', owner: {id: ownerId, name: ownerName}}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "respond_register",
"name": "Respond Register",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
4300,
100
],
"parameters": {
"respondWith": "allIncomingItems",
"options": {}
}
},
{
"id": "prep_lesson",
"name": "Prep Lesson",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1050,
300
],
"parameters": {
"jsCode": "\nconst d=$input.first().json; const C=d.CONFIG;\nconst triggerSales = d.payload?.trigger_sales || false;\nconst lessonNum = Number(d.lesson_order || 0);\n// If lesson_number is 0/null (intro lesson), skip stage update entirely\nconst skipLesson = (!triggerSales && lessonNum === 0);\nconst stageId = triggerSales\n ? C.STAGES.sales_wait\n : (lessonNum > 0 ? (C.STAGES['lesson_' + lessonNum] || '') : '');\nconst ownerId = C.OWNERS && C.OWNERS.length ? C.OWNERS[0].id : '';\nconst leadScore = d.payload?.lead_score || d.user?.lead_score || 0;\n\n// Use bot-provided lead_score_field_json if available\nlet leadScoreFieldJson = d.payload?.lead_score_field_json || '{}';\nif (leadScoreFieldJson === '{}' && C.CUSTOM_FIELDS.lead_score && C.CUSTOM_FIELDS.lead_score !== 'FIELD_GUID') {\n leadScoreFieldJson = JSON.stringify({[C.CUSTOM_FIELDS.lead_score]: String(leadScore)});\n}\n\nreturn [{json:{phone:d.user_phone,phoneSearch:d.phone_search||'',stageId,lessonOrder:d.lesson_order,\ncourseTitle:d.course_title,ownerId,pipelineId:C.PIPELINE_ID,\napiKey:C.DIDAR_API_KEY||'',\ntriggerSales, leadScore, leadScoreFieldJson,\nactivityTypeId:C.ACTIVITY_TYPE_SALES,\nuserName:d.user_name,\nskip: skipLesson, reason: skipLesson ? 'intro lesson (no lesson_number)' : '',\nnoteText:'\\u062f\\u0631\\u0633 '+d.lesson_order+' ('+d.lesson_title+') \\u062a\\u06a9\\u0645\\u06cc\\u0644 - '+d.progress_percent+'%',CONFIG:C}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "if_skip_lesson",
"name": "IF Skip Lesson",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1200,
300
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json.skip}}",
"value2": true
}
]
}
}
},
{
"id": "find_person_lesson",
"name": "Find Person Lesson",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
1450,
250
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "person",
"operation": "search",
"Keywords": "={{$json.phoneSearch}}",
"additionalFields": {
"Limit": 1
}
}
},
{
"id": "extract_person_lesson",
"name": "Extract Person Lesson",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1650,
250
],
"parameters": {
"jsCode": "\nconst prev=$('Prep Lesson').first().json;\nconst resp=$input.first().json;\nconst list = resp?.search_respons?.List || resp?.Response?.List || [];\nconst found = list.length > 0 ? list[0] : null;\nconst personId = found?.Id || null;\nconst lastName = found?.LastName || '';\nif(!personId) return [{json:{...prev,personId:null,skip:true,reason:'person not found'}}];\n\n// Preserve owner from person if approved\nconst personOwnerId = found?.OwnerId || '';\nconst approvedIds = (prev.CONFIG?.OWNERS || []).map(o => o.id);\nconst ownerId = approvedIds.includes(personOwnerId) ? personOwnerId : prev.ownerId;\n\nreturn [{json:{...prev,personId,ownerId,lastName:lastName||prev.userName,skip:false}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "if_person_found_lesson",
"name": "IF Person Found Lesson",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1750,
250
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json.skip}}",
"value2": false
}
]
}
}
},
{
"id": "update_score_lesson",
"name": "Update Score Lesson",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
1850,
250
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "person",
"operation": "update",
"Id": "={{$json.personId}}",
"LastName": "={{$json.lastName}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"additionalFields": {
"Fields": "={{$json.leadScoreFieldJson}}"
}
}
},
{
"id": "recover_person_lesson",
"name": "Recover Person Lesson",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
2050,
250
],
"parameters": {
"jsCode": "\nconst prev=$('Extract Person Lesson').first().json;\nreturn [{json:{...prev}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "search_deal",
"name": "Search Deal V2",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2250,
250
],
"parameters": {
"method": "POST",
"url": "=https://app.didar.me/api/deal/search_v2?apikey={{ $json.apiKey }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({Criteria: {ContactIds: [$json.personId], PipelineId: $json.pipelineId}, From: 0, Limit: 50}) }}",
"options": {}
}
},
{
"id": "extract_deal",
"name": "Extract Deal",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
2450,
250
],
"parameters": {
"jsCode": "\nconst prev=$('Recover Person Lesson').first().json;\nconst resp=$input.first().json;\nconst deals=resp?.Response?.List||resp?.search_respons?.List||[];\nif(!deals.length) return [{json:{...prev,skip:true,reason:'deal not found',\n dealTitle:'\\u062f\\u0648\\u0631\\u0647 ' + prev.courseTitle + ' - ' + (prev.userName || 'User'),\n companyId: prev.CONFIG?.COMPANY_ID || '00000000-0000-0000-0000-000000000000'}}];\nconst deal = deals.find(d => d.PersonId === prev.personId || d.ContactId === prev.personId) || deals[0];\n\n// Preserve owner from deal if approved\nconst dealOwnerId = deal.OwnerId || '';\nconst approvedIds = (prev.CONFIG?.OWNERS || []).map(o => o.id);\nconst realOwner = approvedIds.includes(dealOwnerId) ? dealOwnerId : prev.ownerId;\n\nreturn [{json:{dealId:deal.Id,dealTitle:deal.Title,personId:deal.PersonId||deal.ContactId||prev.personId,\nstageId:prev.stageId,ownerId:realOwner,pipelineId:prev.pipelineId,\ncompanyId:deal.CompanyId||'00000000-0000-0000-0000-000000000000',\ntriggerSales:prev.triggerSales,userName:prev.userName,lessonOrder:prev.lessonOrder,\nactivityTypeId:prev.activityTypeId,leadScore:prev.leadScore,\nnoteText:prev.noteText,CONFIG:prev.CONFIG,skip:false}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "if_deal_found",
"name": "IF Deal Found",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
2650,
250
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json.skip}}",
"value2": false
}
]
}
}
},
{
"id": "update_deal_stage",
"name": "Update Deal Stage",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
2900,
200
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "deal",
"operation": "update",
"Id": "={{$json.dealId}}",
"Title": "={{$json.dealTitle}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"PipelineMode": "manual",
"PipelineId": "={{$json.pipelineId}}",
"StageMode": "manual",
"PipelineStageId": "={{$json.stageId}}",
"PersonId": "={{$json.personId}}",
"CompanyId": "={{$json.companyId}}",
"Status": "Pending",
"LabelIds": [],
"additionalFields": {}
}
},
{
"id": "recover_lesson",
"name": "Recover Lesson Data",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
3150,
200
],
"parameters": {
"jsCode": "\nconst prev=$('Extract Deal').first().json;\nreturn [{json:{...prev}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "if_trigger_sales",
"name": "IF Trigger Sales",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
3400,
200
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json.triggerSales}}",
"value2": true
}
]
}
}
},
{
"id": "sales_call_lesson",
"name": "Sales Call Lesson",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
3650,
150
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "activity",
"operation": "create",
"Title": "=\u062a\u0645\u0627\u0633 \u0641\u0631\u0648\u0634 - {{$json.userName}} (\u062f\u0631\u0633 {{$json.lessonOrder}})",
"ActivityTypeMode": "manual",
"ActivityTypeIdManual": "={{$json.activityTypeId}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"IsDone": false,
"additionalFields": {
"Note": "={{$json.noteText}}",
"ContactIds": "={{$json.personId}}"
}
}
},
{
"id": "create_deal_lesson",
"name": "Create Deal Lesson",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
3200,
672
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "deal",
"operation": "create",
"Title": "={{$json.dealTitle}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"PipelineMode": "manual",
"PipelineId": "={{$json.pipelineId}}",
"StageMode": "manual",
"PipelineStageId": "={{$json.stageId}}",
"PersonId": "={{$json.personId}}",
"CompanyId": "={{$json.companyId}}",
"Status": "Pending",
"LabelIds": [],
"additionalFields": {}
}
},
{
"id": "prep_form",
"name": "Prep Form",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1050,
500
],
"parameters": {
"jsCode": "\nconst d=$input.first().json; const C=d.CONFIG;\nconst f=d.payload?.form_responses||{};\n\n// Use bot-provided crm_form_fields_json if available, else build from CUSTOM_FIELDS\nlet customFieldsJson = d.payload?.crm_form_fields_json || '';\nif (!customFieldsJson) {\n const map={monthly_income:C.CUSTOM_FIELDS.monthly_income,staff_count:C.CUSTOM_FIELDS.staff_count,\n job:C.CUSTOM_FIELDS.job,best_call_time:C.CUSTOM_FIELDS.best_call_time,\n city:C.CUSTOM_FIELDS.city,income_class:C.CUSTOM_FIELDS.income_class};\n let cf={};\n for(const[k,g] of Object.entries(map)){if(f[k]&&g&&g!=='FIELD_GUID')cf[g]=String(f[k]);}\n // Bug 8 fix: include lead_score in custom fields\n const leadScore = d.payload?.lead_score || d.user?.lead_score || 0;\n if (C.CUSTOM_FIELDS.lead_score && C.CUSTOM_FIELDS.lead_score !== 'FIELD_GUID') {\n cf[C.CUSTOM_FIELDS.lead_score] = String(leadScore);\n }\n customFieldsJson = JSON.stringify(cf);\n}\nconst ownerId = C.OWNERS && C.OWNERS.length ? C.OWNERS[0].id : '';\n\n// Build note text from form responses\nconst formTitle = d.payload?.form_title || d.lesson_title || '\u0641\u0631\u0645';\nlet noteLines = ['\\ud83d\\udccb \u067e\u0627\u0633\u062e \u0641\u0631\u0645: ' + formTitle];\nconst allResponses = d.payload?.form_responses || {};\nfor (const [key, val] of Object.entries(allResponses)) {\n noteLines.push('\\u2022 ' + key + ': ' + String(val));\n}\nconst noteText = noteLines.join('\\\\n');\n\nconst firstName = d.user?.registration_data?.first_name || d.user?.first_name || '';\nconst lastName = d.user?.registration_data?.last_name || d.user?.last_name || 'User';\n\nreturn [{json:{phone:d.user_phone,phoneSearch:d.phone_search||'',customFieldsJson,\nownerId,leadScore:d.payload?.lead_score||0,CONFIG:C,noteText,userName:d.user_name,\nfirstName,lastName,lessonTitle:d.lesson_title,\napiKey:C.DIDAR_API_KEY||'',pipelineId:C.PIPELINE_ID,\ncompanyId:C.COMPANY_ID}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "find_person_form",
"name": "Find Person Form",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
1300,
500
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "person",
"operation": "search",
"Keywords": "={{$json.phoneSearch}}",
"additionalFields": {
"Limit": 1
}
}
},
{
"id": "extract_person_form",
"name": "Extract Person Form",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1550,
500
],
"parameters": {
"jsCode": "\nconst prev=$('Prep Form').first().json;\nconst resp=$input.first().json;\nconst list = resp?.search_respons?.List || resp?.Response?.List || [];\nconst found = list.length > 0 ? list[0] : null;\nconst personId = found?.Id || null;\nconst lastName = found?.LastName || prev.lastName || 'User';\nif(!personId) return [{json:{...prev,personId:null,skip:true,reason:'person not found by phone'}}];\n\n// Preserve owner from person if approved\nconst personOwnerId = found?.OwnerId || '';\nconst C = prev.CONFIG;\nconst approvedIds = (C.OWNERS || []).map(o => o.id);\nconst ownerId = approvedIds.includes(personOwnerId) ? personOwnerId : prev.ownerId;\n\nreturn [{json:{...prev,personId,ownerId,lastName,skip:false}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "if_person_found_form",
"name": "IF Person Found Form",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1650,
500
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json.skip}}",
"value2": false
}
]
}
}
},
{
"id": "create_person_form",
"name": "Create Person Form",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
1800,
600
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "person",
"operation": "create",
"FirstName": "={{$json.firstName}}",
"LastName": "={{$json.lastName}}",
"MobilePhone": "={{$json.phone}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"additionalFields": {
"CompanyId": "={{$json.companyId}}",
"VisibilityType": "Owner"
}
}
},
{
"id": "get_new_person_form_id",
"name": "Get New Person Form ID",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1950,
600
],
"parameters": {
"jsCode": "\nconst prev=$('Extract Person Form').first().json;\nconst resp=$input.first().json;\nconst newId=resp?.Response?.Id||'';\nreturn [{json:{...prev,personId:newId,skip:false}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "update_person_fields",
"name": "Update Person Fields",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
2100,
500
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "person",
"operation": "update",
"Id": "={{$json.personId}}",
"LastName": "={{$json.lastName}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"additionalFields": {
"Fields": "={{$json.customFieldsJson}}"
}
}
},
{
"id": "search_deal_form",
"name": "Search Deal Form V2",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2400,
500
],
"parameters": {
"method": "POST",
"url": "=https://app.didar.me/api/deal/search_v2?apikey={{ $json.apiKey }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({Criteria: {ContactIds: [$json.personId], PipelineId: $json.pipelineId}, From: 0, Limit: 50}) }}",
"options": {}
}
},
{
"id": "extract_deal_form",
"name": "Extract Deal Form",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
2650,
500
],
"parameters": {
"jsCode": "\nconst prev=$('Extract Person Form').first().json;\nconst resp=$input.first().json;\nconst deals=resp?.Response?.List||resp?.search_respons?.List||[];\nconst deal=deals.find(d => d.PersonId===prev.personId||d.ContactId===prev.personId)||deals[0]||null;\nreturn [{json:{...prev,dealId:deal?.Id||'',personId:prev.personId}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "create_note_form",
"name": "Create Note Form",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
2900,
500
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "note",
"operation": "create",
"ResultNote": "={{$json.noteText}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"additionalFields": {
"DealId": "={{$json.dealId || ''}}",
"PersonId": "={{$json.personId || ''}}"
}
}
},
{
"id": "recover_form_data",
"name": "Recover Form Data",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
2250,
500
],
"parameters": {
"jsCode": "\nlet prev;\ntry { prev=$('Get New Person Form ID').first().json; }\ncatch(e) { prev=$('Extract Person Form').first().json; }\nreturn [{json:{...prev}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "prep_quiz_pass",
"name": "Prep Quiz Pass",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1050,
680
],
"parameters": {
"jsCode": "\nconst d=$input.first().json; const C=d.CONFIG;\nconst qr=d.payload?.quiz_result||{};\nconst ownerId = C.OWNERS && C.OWNERS.length ? C.OWNERS[0].id : '';\nreturn [{json:{phone:d.user_phone,phoneSearch:d.phone_search||'',ownerId,CONFIG:C,\napiKey:C.DIDAR_API_KEY||'',pipelineId:C.PIPELINE_ID,\ncourseTitle:d.course_title,userName:d.user_name,lessonTitle:d.lesson_title,\nnoteText:'\\u2705 \\u06a9\\u0648\\u06cc\\u06cc\\u0632 \\u067e\\u0627\\u0633! \\u0646\\u0645\\u0631\\u0647: '+qr.score+'/'+qr.passing_score+' \\u062f\\u0631\\u0633: '+d.lesson_title}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "create_note",
"name": "Create Note",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
2800,
755
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "note",
"operation": "create",
"ResultNote": "={{$json.noteText}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"additionalFields": {
"DealId": "={{$json.dealId || ''}}",
"PersonId": "={{$json.personId || ''}}"
}
}
},
{
"id": "prep_quiz_fail",
"name": "Prep Quiz Fail",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1050,
830
],
"parameters": {
"jsCode": "\nconst d=$input.first().json; const C=d.CONFIG;\nconst qr=d.payload?.quiz_result||{};\nconst ownerId = C.OWNERS && C.OWNERS.length ? C.OWNERS[0].id : '';\nreturn [{json:{phone:d.user_phone,phoneSearch:d.phone_search||'',ownerId,CONFIG:C,\napiKey:C.DIDAR_API_KEY||'',pipelineId:C.PIPELINE_ID,\ncourseTitle:d.course_title,userName:d.user_name,lessonTitle:d.lesson_title,\nnoteText:'\\u274c \\u06a9\\u0648\\u06cc\\u06cc\\u0632 \\u0646\\u0627\\u0645\\u0648\\u0641\\u0642! \\u0646\\u0645\\u0631\\u0647: '+qr.score+'/'+qr.passing_score+' \\u062f\\u0631\\u0633: '+d.lesson_title}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "find_person_quiz",
"name": "Find Person Quiz",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
1300,
755
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "person",
"operation": "search",
"Keywords": "={{$json.phoneSearch}}",
"additionalFields": {
"Limit": 1
}
}
},
{
"id": "extract_person_quiz",
"name": "Extract Person Quiz",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1550,
755
],
"parameters": {
"jsCode": "\nlet prev;\ntry { prev=$('Prep Quiz Pass').first().json; }\ncatch(e) { prev=$('Prep Quiz Fail').first().json; }\nconst resp=$input.first().json;\nconst list=resp?.search_respons?.List||resp?.Response?.List||[];\nconst found=list.length>0?list[0]:null;\nconst personId=found?.Id||null;\nif(!personId) return [{json:{...prev,personId:null,dealId:null}}];\nconst personOwnerId=found?.OwnerId||'';\nconst C=prev.CONFIG;\nconst approvedIds=(C.OWNERS||[]).map(o=>o.id);\nconst ownerId=approvedIds.includes(personOwnerId)?personOwnerId:prev.ownerId;\nreturn [{json:{...prev,personId,ownerId}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "search_deal_quiz",
"name": "Search Deal Quiz V2",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1800,
755
],
"parameters": {
"method": "POST",
"url": "=https://app.didar.me/api/deal/search_v2?apikey={{ $json.apiKey }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({Criteria: {ContactIds: [$json.personId], PipelineId: $json.pipelineId}, From: 0, Limit: 50}) }}",
"options": {}
}
},
{
"id": "extract_deal_quiz",
"name": "Extract Deal Quiz",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
2050,
755
],
"parameters": {
"jsCode": "\nconst prev=$('Extract Person Quiz').first().json;\nconst resp=$input.first().json;\nconst deals=resp?.Response?.List||resp?.search_respons?.List||[];\nconst deal=deals.find(d=>d.PersonId===prev.personId||d.ContactId===prev.personId)||deals[0]||null;\nreturn [{json:{...prev,dealId:deal?.Id||''}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "prep_inactivity",
"name": "Prep Inactivity",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1050,
980
],
"parameters": {
"jsCode": "\nconst d=$input.first().json; const C=d.CONFIG;\nconst ownerId = C.OWNERS && C.OWNERS.length ? C.OWNERS[0].id : '';\nreturn [{json:{phone:d.user_phone,phoneSearch:d.phone_search||'',ownerId,CONFIG:C,\nactivityTypeId:C.ACTIVITY_TYPE_FOLLOWUP,\nactivityTitle:'\\u067e\\u06cc\\u06af\\u06cc\\u0631\\u06cc \\u0639\\u062f\\u0645 \\u0641\\u0639\\u0627\\u0644\\u06cc\\u062a - '+d.user_name,\nactivityNote:'\\u063a\\u06cc\\u0631\\u0641\\u0639\\u0627\\u0644 48+ \\u0633\\u0627\\u0639\\u062a. \\u062f\\u0631\\u0633 \\u0622\\u062e\\u0631: '+d.lesson_title+' \\u067e\\u06cc\\u0634\\u0631\\u0641\\u062a: '+d.progress_percent+'%'}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "create_followup",
"name": "Create Followup",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
1300,
980
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "activity",
"operation": "create",
"Title": "={{$json.activityTitle}}",
"ActivityTypeMode": "manual",
"ActivityTypeIdManual": "={{$json.activityTypeId}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"IsDone": false,
"additionalFields": {
"Note": "={{$json.activityNote}}"
}
}
},
{
"id": "prep_complete",
"name": "Prep Complete",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1050,
1150
],
"parameters": {
"jsCode": "\nconst d=$input.first().json; const C=d.CONFIG;\nconst ownerId = C.OWNERS && C.OWNERS.length ? C.OWNERS[0].id : '';\nconst leadScore = d.payload?.lead_score || d.user?.lead_score || 0;\n\n// Use bot-provided lead_score_field_json if available\nlet leadScoreFieldJson = d.payload?.lead_score_field_json || '{}';\nif (leadScoreFieldJson === '{}' && C.CUSTOM_FIELDS.lead_score && C.CUSTOM_FIELDS.lead_score !== 'FIELD_GUID') {\n leadScoreFieldJson = JSON.stringify({[C.CUSTOM_FIELDS.lead_score]: String(leadScore)});\n}\n\nreturn [{json:{phone:d.user_phone,phoneSearch:d.phone_search||'',userName:d.user_name,courseTitle:d.course_title,\nstageId:C.STAGES.sales_wait,ownerId,pipelineId:C.PIPELINE_ID,leadScore,leadScoreFieldJson,\napiKey:C.DIDAR_API_KEY||'',\nactivityTypeId:C.ACTIVITY_TYPE_SALES,CONFIG:C,\nnoteText:'\\ud83c\\udf89 \\u062f\\u0648\\u0631\\u0647 '+d.course_title+' \\u062a\\u06a9\\u0645\\u06cc\\u0644 \\u0634\\u062f!',\nactivityTitle:'\\u062a\\u0645\\u0627\\u0633 \\u0641\\u0631\\u0648\\u0634 - '+d.user_name}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "find_person_complete",
"name": "Find Person Complete",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
1300,
1150
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "person",
"operation": "search",
"Keywords": "={{$json.phoneSearch}}",
"additionalFields": {
"Limit": 1
}
}
},
{
"id": "extract_person_complete",
"name": "Extract Person Complete",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1550,
1150
],
"parameters": {
"jsCode": "\nconst prev=$('Prep Complete').first().json;\nconst resp=$input.first().json;\nconst list = resp?.search_respons?.List || resp?.Response?.List || [];\nconst found = list.length > 0 ? list[0] : null;\nconst personId = found?.Id || null;\nconst lastName = found?.LastName || '';\nif(!personId) return [{json:{...prev,personId:null,skip:true,reason:'person not found'}}];\n\n// Preserve owner from person if approved\nconst personOwnerId = found?.OwnerId || '';\nconst approvedIds = (prev.CONFIG?.OWNERS || []).map(o => o.id);\nconst ownerId = approvedIds.includes(personOwnerId) ? personOwnerId : prev.ownerId;\n\nreturn [{json:{...prev,personId,ownerId,lastName:lastName||prev.userName,skip:false}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "if_person_found_complete",
"name": "IF Person Found Complete",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1650,
1150
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json.skip}}",
"value2": false
}
]
}
}
},
{
"id": "update_score_complete",
"name": "Update Score Complete",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
1750,
1150
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "person",
"operation": "update",
"Id": "={{$json.personId}}",
"LastName": "={{$json.lastName}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"additionalFields": {
"Fields": "={{$json.leadScoreFieldJson}}"
}
}
},
{
"id": "recover_person_complete",
"name": "Recover Person Complete",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1950,
1150
],
"parameters": {
"jsCode": "\nconst prev=$('Extract Person Complete').first().json;\nreturn [{json:{...prev}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "search_deal_complete",
"name": "Search Deal Complete V2",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2150,
1150
],
"parameters": {
"method": "POST",
"url": "=https://app.didar.me/api/deal/search_v2?apikey={{ $json.apiKey }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({Criteria: {ContactIds: [$json.personId], PipelineId: $json.pipelineId}, From: 0, Limit: 50}) }}",
"options": {}
}
},
{
"id": "extract_deal_complete",
"name": "Extract Deal Complete",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
2350,
1150
],
"parameters": {
"jsCode": "\nconst prev=$('Recover Person Complete').first().json;\nconst resp=$input.first().json;\nconst deals=resp?.Response?.List||resp?.search_respons?.List||[];\nif(!deals.length) return [{json:{...prev,skip:true,reason:'deal not found for completion',\n dealTitle:'\\u062f\\u0648\\u0631\\u0647 ' + prev.courseTitle + ' - ' + (prev.userName || 'User'),\n companyId: prev.CONFIG?.COMPANY_ID || '00000000-0000-0000-0000-000000000000'}}];\nconst deal = deals.find(d => d.PersonId === prev.personId || d.ContactId === prev.personId) || deals[0];\n\n// Preserve owner from deal if approved\nconst dealOwnerId = deal.OwnerId || '';\nconst approvedIds = (prev.CONFIG?.OWNERS || []).map(o => o.id);\nconst realOwner = approvedIds.includes(dealOwnerId) ? dealOwnerId : prev.ownerId;\n\nreturn [{json:{...prev,ownerId:realOwner,dealId:deal.Id,dealTitle:deal.Title,\npersonId:deal.PersonId||deal.ContactId||prev.personId,\ncompanyId:deal.CompanyId||'00000000-0000-0000-0000-000000000000',skip:false}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "if_deal_found_complete",
"name": "IF Deal Found Complete",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
2550,
1150
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json.skip}}",
"value2": false
}
]
}
}
},
{
"id": "update_deal_complete",
"name": "Update Deal Complete",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
2800,
1100
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "deal",
"operation": "update",
"Id": "={{$json.dealId}}",
"Title": "={{$json.dealTitle}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"PipelineMode": "manual",
"PipelineId": "={{$json.pipelineId}}",
"StageMode": "manual",
"PipelineStageId": "={{$json.stageId}}",
"PersonId": "={{$json.personId}}",
"CompanyId": "={{$json.companyId}}",
"Status": "Won",
"LabelIds": [],
"additionalFields": {}
}
},
{
"id": "recover_complete",
"name": "Recover Complete Data",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
3050,
1100
],
"parameters": {
"jsCode": "\nconst prev=$('Extract Deal Complete').first().json;\nreturn [{json:{...prev}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "sales_call",
"name": "Sales Call",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
3300,
1100
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "activity",
"operation": "create",
"Title": "={{$json.activityTitle}}",
"ActivityTypeMode": "manual",
"ActivityTypeIdManual": "={{$json.activityTypeId}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"IsDone": false,
"additionalFields": {
"Note": "={{$json.noteText}}",
"ContactIds": "={{$json.personId}}"
}
}
},
{
"id": "create_deal_complete2",
"name": "Create Deal Complete2",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
2800,
1300
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "deal",
"operation": "create",
"Title": "={{$json.dealTitle}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"PipelineMode": "manual",
"PipelineId": "={{$json.pipelineId}}",
"StageMode": "manual",
"PipelineStageId": "={{$json.stageId}}",
"PersonId": "={{$json.personId}}",
"CompanyId": "={{$json.companyId}}",
"Status": "Won",
"LabelIds": [],
"additionalFields": {}
}
},
{
"id": "after_create_deal_complete",
"name": "After Create Deal Complete",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
3050,
1300
],
"parameters": {
"jsCode": "\nconst prev=$('Extract Deal Complete').first().json;\nconst resp=$input.first().json;\nconst newDealId=resp?.Response?.Id||'';\nreturn [{json:{...prev,dealId:newDealId,skip:false}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "unify_deal_complete",
"name": "Unify Deal Complete",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
3300,
1200
],
"parameters": {
"jsCode": "\nlet prev, dealId;\ntry {\n prev=$('Recover Complete Data').first().json;\n dealId=prev.dealId;\n} catch(e) {}\nif (!dealId) {\n try {\n prev=$('After Create Deal Complete').first().json;\n dealId=prev.dealId;\n } catch(e) {}\n}\nif (!prev) prev={};\nreturn [{json:{...prev,dealId:dealId||''}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "prep_speed",
"name": "Prep Speed",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1050,
1350
],
"parameters": {
"jsCode": "\nconst d=$input.first().json; const C=d.CONFIG;\nconst ownerId = C.OWNERS && C.OWNERS.length ? C.OWNERS[0].id : '';\nreturn [{json:{phone:d.user_phone,phoneSearch:d.phone_search||'',ownerId,CONFIG:C,\nnoteText:'\\u26a1 \\u062a\\u063a\\u06cc\\u06cc\\u0631 \\u0633\\u0631\\u0639\\u062a: '+(d.payload?.speed_type||d.event?.type||'')+' \\u2192 '+(d.payload?.new_speed||d.event?.status||'')}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "find_person_speed",
"name": "Find Person Speed",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
1300,
1350
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "person",
"operation": "search",
"Keywords": "={{$json.phoneSearch}}",
"additionalFields": {
"Limit": 1
}
}
},
{
"id": "extract_person_speed",
"name": "Extract Person Speed",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
1550,
1350
],
"parameters": {
"jsCode": "\nconst prev=$('Prep Speed').first().json;\nconst resp=$input.first().json;\nconst list=resp?.search_respons?.List||resp?.Response?.List||[];\nconst found=list.length>0?list[0]:null;\nconst personId=found?.Id||null;\nif(!personId) return [{json:{...prev,personId:null}}];\nconst personOwnerId=found?.OwnerId||'';\nconst C=prev.CONFIG;\nconst approvedIds=(C.OWNERS||[]).map(o=>o.id);\nconst ownerId=approvedIds.includes(personOwnerId)?personOwnerId:prev.ownerId;\nreturn [{json:{...prev,personId,ownerId}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "create_note_speed",
"name": "Create Note Speed",
"type": "n8n-nodes-didar-crm.didarCrm",
"typeVersion": 1,
"position": [
1800,
1350
],
"credentials": {
"didarApi": {
"name": "<your credential>"
}
},
"parameters": {
"resource": "note",
"operation": "create",
"ResultNote": "={{$json.noteText}}",
"OwnerMode": "manual",
"OwnerIdManual": "={{$json.ownerId}}",
"additionalFields": {
"PersonId": "={{$json.personId || ''}}"
}
}
},
{
"id": "prep_respond_ok",
"name": "Prep Respond OK",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
2050,
600
],
"parameters": {
"jsCode": "\nreturn [{json: {status: 'ok'}}];\n",
"mode": "runOnceForAllItems"
}
},
{
"id": "respond_ok",
"name": "Respond OK",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
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.
didarApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Course Bot - Didar CRM (v6). Uses n8n-nodes-didar-crm, httpRequest. Webhook trigger; 77 nodes.
Source: https://github.com/javid1371/telegram-course-bot/blob/85c7067292d185aafcb8ebe2f539e59cf032950c/n8n-workflows/course-bot-didar-crm.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.
Advanced Slackbot With N8N. Uses slack, httpRequest, stickyNote, executeWorkflow. Webhook trigger; 34 nodes.
Slackbots are super powerful. At n8n, we have been using them to get a lot done.. But it can become hard to manage and maintain many different operations that a workflow can do.
Standup Bot 4 4 Worker. Uses mattermost, httpRequest, noOp, executeWorkflow. Webhook trigger; 29 nodes.
This is the fourth workflow for the Mattermost Standup Bot. This workflow sends the team a message every morning to ask them three standup questions. What have you accomplished since your last report?
SmartPoll Automator is an n8n-powered workflow that automatically creates and publishes polls in Telegram. It helps teams, communities, and businesses run polls without manual work.