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": "WhatsApp Templates - Secure Credentials",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "send-whatsapp-template",
"responseMode": "responseNode",
"options": {}
},
"id": "05756597-b904-4953-b13f-ee9fa290110c",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
13280,
9888
]
},
{
"parameters": {
"jsCode": "// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// PrepareData - \u0627\u0633\u062a\u062e\u0631\u0627\u062c \u0648\u0627\u0644\u062a\u062d\u0642\u0642 \u0645\u0646 \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a\n// \ud83d\udd10 \u0627\u0644\u062a\u0648\u0643\u0646 \u064a\u064f\u0642\u0631\u0623 \u0645\u0646 n8n Credentials (\u0623\u0645\u0627\u0646 \u0623\u0639\u0644\u0649)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nconst inputData = $input.first().json;\nconst data = inputData.body || inputData;\n\n// \u0627\u0644\u062a\u062d\u0642\u0642 \u0645\u0646 \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a \u0627\u0644\u0645\u0637\u0644\u0648\u0628\u0629 (\u0628\u062f\u0648\u0646 accessToken - \u064a\u064f\u0642\u0631\u0623 \u0645\u0646 Credentials)\nconst templateType = data.templateType;\nconst recipients = data.recipients || [];\nconst phoneNumberId = data.phoneNumberId || '';\n\nif (!templateType) {\n return [{ json: { success: false, error: 'templateType \u0645\u0637\u0644\u0648\u0628', hasError: true } }];\n}\nif (!phoneNumberId) {\n return [{ json: { success: false, error: 'phoneNumberId \u0645\u0637\u0644\u0648\u0628', hasError: true } }];\n}\nif (!recipients || recipients.length === 0) {\n return [{ json: { success: false, error: 'recipients \u0645\u0637\u0644\u0648\u0628\u0629', hasError: true } }];\n}\n\nconst defaultValues = {\n contactNumbers: data.contactNumbers || '07705210210 - 07717727720',\n location: data.location || '\u0628\u063a\u062f\u0627\u062f/\u062d\u064a \u0627\u0644\u0627\u0645\u0627\u0646\u0629 https://maps.app.goo.gl/hfYqRMZNr2qYnsV3A',\n offerText: data.offerText || '\u0644\u062f\u064a\u0646\u0627 \u0639\u0631\u0648\u0636 \u0645\u0645\u064a\u0632\u0629 \u0644\u0643!'\n};\n\nconst config = {\n maxRetries: data.maxRetries || 3,\n progressUpdateEvery: data.progressUpdateEvery || 100\n};\n\nconst batchId = `batch_${Date.now()}`;\nconst total = recipients.length;\n\nreturn recipients.map((r, index) => {\n let phone = (r.phoneNumber || r.phone || '').toString().trim();\n if (phone.startsWith('0')) {\n phone = '964' + phone.substring(1);\n } else if (!phone.startsWith('964') && !phone.startsWith('+964')) {\n phone = '964' + phone;\n }\n phone = phone.replace('+', '').replace(/[^\\d]/g, '');\n \n const valid = /^964\\d{9,12}$/.test(phone);\n \n return {\n json: {\n index: index + 1,\n total: total,\n batchId: batchId,\n name: r.name || '\u0639\u0632\u064a\u0632\u064a \u0627\u0644\u0645\u0634\u062a\u0631\u0643',\n phone: phone,\n originalPhone: r.phoneNumber || r.phone || '',\n expiryDate: r.expiryDate || r.expires || '',\n planName: r.planName || r.plan || '',\n price: r.price || '',\n templateType: templateType,\n phoneNumberId: phoneNumberId,\n contactNumbers: r.contactNumbers || defaultValues.contactNumbers,\n location: r.location || defaultValues.location,\n offerText: r.offerText || defaultValues.offerText,\n valid: valid,\n err: valid ? '' : 'Invalid phone format',\n hasError: false,\n retryCount: 0,\n config: config\n }\n };\n});"
},
"id": "bf433526-9b51-4bee-b133-2fe8e58e03bb",
"name": "PrepareData",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
13504,
9888
]
},
{
"parameters": {
"jsCode": "// \u062a\u062c\u0647\u064a\u0632 \u0627\u0644\u0631\u062f \u0627\u0644\u0641\u0648\u0631\u064a\nconst items = $items('PrepareData').map(i => i.json);\nconst first = items[0] || {};\n\nif (first.hasError) {\n return [{\n json: {\n immediateResponse: {\n success: false,\n error: first.error\n },\n hasError: true\n }\n }];\n}\n\nconst totalCount = items.length;\nconst batchId = first.batchId || `batch_${Date.now()}`;\nconst templateType = first.templateType || '';\nconst config = first.config || {};\n\nreturn [{\n json: {\n immediateResponse: {\n success: true,\n status: 'processing',\n message: '\u062a\u0645 \u0627\u0633\u062a\u0644\u0627\u0645 \u0627\u0644\u0637\u0644\u0628 \u0648\u062c\u0627\u0631\u064a \u0625\u0631\u0633\u0627\u0644 \u0627\u0644\u0631\u0633\u0627\u0626\u0644 \u0641\u064a \u0627\u0644\u062e\u0644\u0641\u064a\u0629',\n batchId,\n templateType,\n totalRecipients: totalCount,\n estimatedTime: Math.ceil(totalCount * 1.5) + ' \u062b\u0627\u0646\u064a\u0629',\n features: {\n retryEnabled: true,\n maxRetries: config.maxRetries || 3,\n secureCredentials: true\n },\n timestamp: new Date().toISOString()\n },\n processItems: items,\n batchId,\n totalCount,\n config,\n hasError: false\n }\n}];"
},
"id": "0fac9f87-c8ff-40cb-b2e2-0fa1af6cf313",
"name": "PrepareImmediateResponse",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
13728,
9888
],
"executeOnce": true
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json.immediateResponse }}",
"options": {
"responseCode": 200
}
},
"id": "6e9c5500-a45e-448a-a476-cb9711c305fc",
"name": "RespondImmediately",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
13920,
9744
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"id": "no-error",
"leftValue": "={{ $json.hasError }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "notEquals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "b9ade8de-c8eb-4442-982c-00c799965e13",
"name": "CheckHasError",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
13920,
9888
]
},
{
"parameters": {
"jsCode": "// \u0627\u0633\u062a\u062e\u0631\u0627\u062c \u0627\u0644\u0639\u0646\u0627\u0635\u0631 \u0644\u0644\u0645\u0639\u0627\u0644\u062c\u0629\nconst data = $json;\nconst items = data.processItems || [];\nconst config = data.config || {};\nreturn items.map(item => ({ json: { ...item, config } }));"
},
"id": "f6e656f6-6e1d-44cd-9d45-dfea533688aa",
"name": "ExtractItems",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
14144,
9872
]
},
{
"parameters": {
"options": {}
},
"id": "17775c9f-1076-4eea-b3ef-93fab0a13da0",
"name": "SplitBatches",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
14368,
9872
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"id": "valid",
"leftValue": "={{ $json.valid }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "862f98ce-7aa0-40d6-9401-539375023be7",
"name": "CheckPhone",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
14528,
9888
]
},
{
"parameters": {
"jsCode": "// \u062a\u062d\u0636\u064a\u0631 \u0628\u064a\u0627\u0646\u0627\u062a \u0627\u0644\u0642\u0627\u0644\u0628 \u062d\u0633\u0628 \u0627\u0644\u0646\u0648\u0639\nconst item = $input.first().json;\nconst templateType = item.templateType;\n\nlet templateName = '';\nlet components = [];\nlet messageText = '';\n\nconst name = item.name || '\u0639\u0632\u064a\u0632\u064a \u0627\u0644\u0645\u0634\u062a\u0631\u0643';\nconst expiryDate = item.expiryDate || '';\nconst planName = item.planName || '';\nconst price = item.price || '';\nconst contactNumbers = item.contactNumbers;\nconst location = item.location;\nconst offerText = item.offerText;\n\nswitch (templateType) {\n case 'sadara_reminder':\n templateName = 'sadara_reminder';\n components = [\n {\n \"type\": \"body\",\n \"parameters\": [\n { \"type\": \"text\", \"text\": name },\n { \"type\": \"text\", \"text\": expiryDate },\n { \"type\": \"text\", \"text\": contactNumbers },\n { \"type\": \"text\", \"text\": location }\n ]\n }\n ];\n messageText = `\u0645\u0631\u062d\u0628\u0627\u064b ${name} \ud83d\udc4b\\n\\n\u0646\u0648\u062f \u062a\u0630\u0643\u064a\u0631\u0643 \u0628\u0623\u0646 \u0627\u0634\u062a\u0631\u0627\u0643\u0643 \u0641\u064a \u062e\u062f\u0645\u0629 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0633\u064a\u0646\u062a\u0647\u064a \u0628\u062a\u0627\u0631\u064a\u062e ${expiryDate}.\\n\\n\u0644\u0644\u062a\u062c\u062f\u064a\u062f \u0623\u0648 \u0627\u0644\u0627\u0633\u062a\u0641\u0633\u0627\u0631:\\n${contactNumbers}\\n\\n\ud83d\udccd ${location}`;\n break;\n \n case 'sadara_renewed':\n templateName = 'sadara_renewed';\n components = [\n {\n \"type\": \"body\",\n \"parameters\": [\n { \"type\": \"text\", \"text\": name },\n { \"type\": \"text\", \"text\": planName },\n { \"type\": \"text\", \"text\": price.toString() },\n { \"type\": \"text\", \"text\": expiryDate },\n { \"type\": \"text\", \"text\": contactNumbers },\n { \"type\": \"text\", \"text\": location }\n ]\n }\n ];\n messageText = `\u0645\u0631\u062d\u0628\u0627\u064b ${name} \ud83c\udf89\\n\\n\u062a\u0645 \u062a\u062c\u062f\u064a\u062f \u0627\u0634\u062a\u0631\u0627\u0643\u0643 \u0628\u0646\u062c\u0627\u062d!\\n\\n\u0627\u0644\u0628\u0627\u0642\u0629: ${planName}\\n\u0627\u0644\u0645\u0628\u0644\u063a: ${price} \u062f.\u0639\\n\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0627\u0646\u062a\u0647\u0627\u0621 \u0627\u0644\u062c\u062f\u064a\u062f: ${expiryDate}\\n\\n\u0644\u0644\u0627\u0633\u062a\u0641\u0633\u0627\u0631:\\n${contactNumbers}\\n\\n\ud83d\udccd ${location}`;\n break;\n \n case 'sadara_expired':\n templateName = 'sadara_expired';\n components = [\n {\n \"type\": \"body\",\n \"parameters\": [\n { \"type\": \"text\", \"text\": name },\n { \"type\": \"text\", \"text\": expiryDate },\n { \"type\": \"text\", \"text\": offerText },\n { \"type\": \"text\", \"text\": contactNumbers },\n { \"type\": \"text\", \"text\": location }\n ]\n }\n ];\n messageText = `\u0645\u0631\u062d\u0628\u0627\u064b ${name} \ud83d\udc4b\\n\\\u0646\u0644\u0627\u062d\u0638 \u0623\u0646 \u0627\u0634\u062a\u0631\u0627\u0643\u0643 \u0642\u062f \u0627\u0646\u062a\u0647\u0649 \u0628\u062a\u0627\u0631\u064a\u062e ${expiryDate}.\\n\\n\ud83c\udf81 \u0639\u0631\u0636 \u062e\u0627\u0635: ${offerText}\\n\\n\u0644\u0644\u062a\u062c\u062f\u064a\u062f:\\n${contactNumbers}\\n\\n\ud83d\udccd ${location}`;\n break;\n \n default:\n templateName = 'sadara_reminder';\n components = [\n {\n \"type\": \"body\",\n \"parameters\": [\n { \"type\": \"text\", \"text\": name },\n { \"type\": \"text\", \"text\": expiryDate },\n { \"type\": \"text\", \"text\": contactNumbers },\n { \"type\": \"text\", \"text\": location }\n ]\n }\n ];\n messageText = `\u0645\u0631\u062d\u0628\u0627\u064b ${name} \ud83d\udc4b\\n\\\u0646\u0648\u062f \u062a\u0630\u0643\u064a\u0631\u0643 \u0628\u0623\u0646 \u0627\u0634\u062a\u0631\u0627\u0643\u0643 \u0633\u064a\u0646\u062a\u0647\u064a \u0628\u062a\u0627\u0631\u064a\u062e ${expiryDate}.\\n\\n\u0644\u0644\u062a\u062c\u062f\u064a\u062f:\\n${contactNumbers}\\n\\n\ud83d\udccd ${location}`;\n}\n\nreturn {\n index: item.index,\n total: item.total,\n batchId: item.batchId,\n phone: item.phone,\n name: name,\n templateType: templateType,\n templateName: templateName,\n components: components,\n messageText: messageText,\n phoneNumberId: item.phoneNumberId,\n expiryDate: expiryDate,\n planName: planName,\n price: price,\n retryCount: item.retryCount || 0,\n config: item.config\n};"
},
"id": "480cef80-e2b9-46d0-ba81-f7dbaa22d4d4",
"name": "BuildTemplate",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
14736,
9792
]
},
{
"parameters": {
"method": "POST",
"url": "=https://graph.facebook.com/v21.0/{{ $json.phoneNumberId }}/messages",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "whatsAppApi",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"messaging_product\": \"whatsapp\",\n \"to\": \"{{ $json.phone }}\",\n \"type\": \"template\",\n \"template\": {\n \"name\": \"{{ $json.templateName }}\",\n \"language\": {\n \"code\": \"ar\"\n },\n \"components\": {{ JSON.stringify($json.components) }}\n }\n}",
"options": {
"response": {
"response": {
"fullResponse": true,
"neverError": true
}
}
}
},
"id": "3113a3e5-832b-4b43-85c5-608cb32951e8",
"name": "SendWhatsApp",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
14896,
9792
],
"credentials": {
"whatsAppApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// ProcessResult - \u0645\u0639\u0627\u0644\u062c\u0629 \u0646\u062a\u064a\u062c\u0629 \u0627\u0644\u0625\u0631\u0633\u0627\u0644 \u0645\u0639 \u062f\u0639\u0645 Retry \u0648 Auth Error\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nconst response = $input.first().json;\nconst templateData = $('BuildTemplate').first().json;\nconst config = templateData.config || {};\nconst maxRetries = config.maxRetries || 3;\n\nlet success = false;\nlet messageId = '';\nlet errorMessage = '';\nlet errorCode = '';\nlet shouldRetry = false;\nlet isAuthError = false;\n\nif (response.body && response.body.messages && response.body.messages[0]) {\n success = true;\n messageId = response.body.messages[0].id;\n} else if (response.body && response.body.error) {\n errorCode = response.body.error.code || '';\n errorMessage = response.body.error.message || 'Unknown error';\n \n // \u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0623\u062e\u0637\u0627\u0621\n const retryableCodes = [1, 2, 4, 17, 341, 368, 130429];\n const authErrorCodes = [190, 102, 104, 200];\n \n if (authErrorCodes.includes(Number(errorCode))) {\n isAuthError = true;\n errorMessage = '\ud83d\udd34 \u062e\u0637\u0623 \u0645\u0635\u0627\u062f\u0642\u0629 - \u062a\u062d\u0642\u0642 \u0645\u0646 WhatsApp Access Token \u0641\u064a Credentials';\n } else if (retryableCodes.includes(Number(errorCode))) {\n shouldRetry = templateData.retryCount < maxRetries;\n errorMessage = `\u062e\u0637\u0623 \u0645\u0624\u0642\u062a (${errorCode}) - ${shouldRetry ? '\u0633\u064a\u062a\u0645 \u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u0645\u062d\u0627\u0648\u0644\u0629' : '\u0627\u0633\u062a\u0646\u0641\u062f\u062a \u0627\u0644\u0645\u062d\u0627\u0648\u0644\u0627\u062a'}`;\n } else {\n if (errorCode == 131030) errorMessage = '\u0631\u0642\u0645 \u0627\u0644\u0647\u0627\u062a\u0641 \u063a\u064a\u0631 \u0635\u0627\u0644\u062d \u0623\u0648 \u063a\u064a\u0631 \u0645\u0633\u062c\u0644 \u0641\u064a WhatsApp';\n else if (errorCode == 131026) errorMessage = '\u0627\u0644\u0631\u0633\u0627\u0644\u0629 \u0644\u0645 \u062a\u064f\u0631\u0633\u0644 - \u062a\u0623\u0643\u062f \u0645\u0646 \u0646\u0627\u0641\u0630\u0629 \u0627\u0644\u0640 24 \u0633\u0627\u0639\u0629';\n else if (errorCode == 132000) errorMessage = '\u0639\u062f\u062f \u0627\u0644\u0645\u062a\u063a\u064a\u0631\u0627\u062a \u0644\u0627 \u064a\u062a\u0637\u0627\u0628\u0642 \u0645\u0639 \u0627\u0644\u0642\u0627\u0644\u0628';\n else if (errorCode == 132001) errorMessage = '\u0627\u0644\u0642\u0627\u0644\u0628 \u063a\u064a\u0631 \u0645\u0648\u062c\u0648\u062f \u0623\u0648 \u063a\u064a\u0631 \u0645\u0639\u062a\u0645\u062f';\n }\n} else if (response.statusCode && response.statusCode !== 200) {\n errorMessage = `HTTP Error: ${response.statusCode}`;\n shouldRetry = templateData.retryCount < maxRetries && response.statusCode >= 500;\n} else {\n errorMessage = 'No response from WhatsApp API';\n shouldRetry = templateData.retryCount < maxRetries;\n}\n\nconst ts = Math.floor(Date.now() / 1000);\nconst tsMs = Date.now();\nconst msgId = messageId ? messageId.replace(/\\//g, '_') : 'msg_' + ts + '_' + templateData.index;\n\nconsole.log(`[${templateData.index}/${templateData.total}] Phone: ${templateData.phone}, Success: ${success}, Retry: ${templateData.retryCount}/${maxRetries}`);\n\nreturn [{\n json: {\n index: templateData.index,\n total: templateData.total,\n batchId: templateData.batchId,\n ok: success,\n messageId: messageId,\n msgId: msgId,\n err: errorMessage,\n errorCode: errorCode,\n phone: templateData.phone,\n name: templateData.name,\n templateType: templateData.templateType,\n templateName: templateData.templateName,\n messageText: templateData.messageText,\n ts: ts,\n tsMs: tsMs,\n retryCount: templateData.retryCount,\n shouldRetry: shouldRetry && !success,\n isAuthError: isAuthError,\n config: config,\n phoneNumberId: templateData.phoneNumberId,\n components: templateData.components,\n expiryDate: templateData.expiryDate,\n planName: templateData.planName,\n price: templateData.price\n }\n}];"
},
"id": "44b76f91-03a0-48dd-ad82-6ff5fb1f2ddb",
"name": "ProcessResult",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
15056,
9792
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"id": "authError",
"leftValue": "={{ $json.isAuthError }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "7086bf35-12d4-4aa4-8e9a-b777ee04539b",
"name": "CheckAuthError",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
15216,
9792
]
},
{
"parameters": {
"jsCode": "// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// AuthErrorStop - \u0625\u064a\u0642\u0627\u0641 \u0645\u0628\u0643\u0631 \u0639\u0646\u062f \u062e\u0637\u0623 \u0645\u0635\u0627\u062f\u0642\u0629\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nconst data = $input.first().json;\n\nconsole.log('\ud83d\udd34 AUTH ERROR DETECTED - STOPPING BATCH');\nconsole.log('Batch ID:', data.batchId);\nconsole.log('Stopped at message:', data.index, 'of', data.total);\nconsole.log('\ud83d\udca1 \u062a\u0623\u0643\u062f \u0645\u0646 \u0635\u062d\u0629 WhatsApp Access Token \u0641\u064a Credentials');\n\nreturn [{ json: {\n success: false,\n warning: '\ud83d\udd34 \u062a\u0645 \u0625\u064a\u0642\u0627\u0641 \u0627\u0644\u0625\u0631\u0633\u0627\u0644 \u0628\u0633\u0628\u0628 \u062e\u0637\u0623 \u0645\u0635\u0627\u062f\u0642\u0629! \u062a\u062d\u0642\u0642 \u0645\u0646 WhatsApp Access Token \u0641\u064a Credentials',\n batchId: data.batchId,\n total: data.total,\n sent: data.index - 1,\n failed: data.total - data.index + 1,\n stoppedAt: data.index,\n rate: data.total ? ((data.index - 1) / data.total * 100).toFixed(1) + '%' : '0%',\n completedAt: new Date().toISOString(),\n failedSummary: 'Auth Error: ' + data.err,\n earlyStop: true,\n earlyStopReason: 'AUTH_ERROR'\n} }];"
},
"id": "1a5b65a2-489f-4dcf-a83b-545396590455",
"name": "AuthErrorStop",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
15392,
9680
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"id": "shouldRetry",
"leftValue": "={{ $json.shouldRetry }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "29655aa8-73d0-466b-b0fa-989199693222",
"name": "CheckShouldRetry",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
15392,
9872
]
},
{
"parameters": {
"amount": 2
},
"id": "a243b303-ffec-4eb7-98f8-8879efc54c51",
"name": "RetryWait",
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
15600,
9776
]
},
{
"parameters": {
"jsCode": "// \u062a\u062c\u0647\u064a\u0632 \u0627\u0644\u0628\u064a\u0627\u0646\u0627\u062a \u0644\u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u0645\u062d\u0627\u0648\u0644\u0629\nconst data = $input.first().json;\nconsole.log(`\ud83d\udd04 Retry attempt ${data.retryCount + 1} for phone: ${data.phone}`);\n\nreturn [{\n json: {\n index: data.index,\n total: data.total,\n batchId: data.batchId,\n phone: data.phone,\n name: data.name,\n templateType: data.templateType,\n templateName: data.templateName,\n components: data.components,\n messageText: data.messageText,\n phoneNumberId: data.phoneNumberId,\n expiryDate: data.expiryDate,\n planName: data.planName,\n price: data.price,\n retryCount: data.retryCount + 1,\n config: data.config\n }\n}];"
},
"id": "8f124aff-4264-4a63-b005-d12e60e59013",
"name": "PrepareRetry",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
15760,
9776
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"id": "isSuccess",
"leftValue": "={{ $json.ok }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "c223a2b1-4d75-45b4-8791-84671328b6e8",
"name": "CheckSuccess",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
15600,
9984
]
},
{
"parameters": {
"method": "POST",
"url": "=https://firestore.googleapis.com/v1/projects/ramz-alsadara2025/databases/(default)/documents/whatsapp_conversations/{{ $json.phone }}/messages?documentId=sent_{{ $json.msgId }}",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleApi",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"fields\": {\n \"messageId\": { \"stringValue\": \"{{ $json.messageId }}\" },\n \"phoneNumber\": { \"stringValue\": \"{{ $json.phone }}\" },\n \"contactName\": { \"stringValue\": \"{{ $json.name }}\" },\n \"text\": { \"stringValue\": \"{{ $json.messageText.replace(/\\\"/g, '\\\\\"').replace(/\\n/g, '\\\\n') }}\" },\n \"templateType\": { \"stringValue\": \"{{ $json.templateType }}\" },\n \"timestamp\": { \"integerValue\": \"{{ $json.ts }}\" },\n \"direction\": { \"stringValue\": \"outgoing\" },\n \"status\": { \"stringValue\": \"sent\" },\n \"messageType\": { \"stringValue\": \"template\" },\n \"batchId\": { \"stringValue\": \"{{ $json.batchId }}\" }\n }\n}",
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"id": "f457efce-dcc7-49d0-937c-7ab95387c711",
"name": "SaveMessage",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
15792,
9904
],
"credentials": {
"googleApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "PATCH",
"url": "=https://firestore.googleapis.com/v1/projects/ramz-alsadara2025/databases/(default)/documents/whatsapp_conversations/{{ $('ProcessResult').item.json.phone }}?updateMask.fieldPaths=phoneNumber&updateMask.fieldPaths=contactName&updateMask.fieldPaths=lastMessage&updateMask.fieldPaths=lastMessageTime&updateMask.fieldPaths=updatedAt",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleApi",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"fields\": {\n \"phoneNumber\": { \"stringValue\": \"{{ $('ProcessResult').item.json.phone }}\" },\n \"contactName\": { \"stringValue\": \"{{ $('ProcessResult').item.json.name }}\" },\n \"lastMessage\": { \"stringValue\": \"{{ $('ProcessResult').item.json.messageText.substring(0, 100).replace(/\\\"/g, '\\\\\"').replace(/\\n/g, ' ') }}\" },\n \"lastMessageTime\": { \"integerValue\": \"{{ $('ProcessResult').item.json.ts }}\" },\n \"updatedAt\": { \"integerValue\": \"{{ $('ProcessResult').item.json.tsMs }}\" }\n }\n}",
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"id": "00bf6879-b57c-41fe-bf04-5f8c75ad3553",
"name": "UpdateConversation",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
15952,
9904
],
"credentials": {
"googleApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"amount": 1
},
"id": "81b4ae5c-9583-4926-9180-9ada5de02032",
"name": "Wait",
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
16112,
9904
]
},
{
"parameters": {
"jsCode": "// \u062a\u0645\u0631\u064a\u0631 \u0646\u062a\u064a\u062c\u0629 \u0627\u0644\u0646\u062c\u0627\u062d\nconst d = $('ProcessResult').item.json;\nreturn [{ json: { index: d.index, total: d.total, batchId: d.batchId, name: d.name, phone: d.phone, ok: true, err: '', msgId: d.msgId } }];"
},
"id": "f41b67ad-6de7-4464-b7af-92bbcd7a93d0",
"name": "SendResultSuccess",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
16272,
9904
]
},
{
"parameters": {
"jsCode": "// \u062a\u0645\u0631\u064a\u0631 \u0646\u062a\u064a\u062c\u0629 \u0627\u0644\u0641\u0634\u0644\nconst d = $input.first().json;\nreturn [{ json: { index: d.index, total: d.total, batchId: d.batchId, name: d.name, phone: d.phone, ok: false, err: d.err, msgId: '' } }];"
},
"id": "163b424c-bd49-4b5a-b5b5-973aa066a6cc",
"name": "SendResultFailed",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
15792,
10080
]
},
{
"parameters": {
"jsCode": "// \u0646\u062a\u064a\u062c\u0629 \u0631\u0642\u0645 \u063a\u064a\u0631 \u0635\u0627\u0644\u062d\nconst item = $input.first().json;\nreturn [{ json: { index: item.index, total: item.total, batchId: item.batchId, name: item.name, phone: item.phone, ok: false, err: item.err || 'Invalid phone format', msgId: '' } }];"
},
"id": "86f6bdc8-3a34-4323-925d-a79bb238bb9f",
"name": "InvalidPhoneResult",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
14736,
10000
]
},
{
"parameters": {
"jsCode": "// \u062a\u062c\u0645\u064a\u0639 \u0627\u0644\u0646\u062a\u0627\u0626\u062c \u0627\u0644\u0646\u0647\u0627\u0626\u064a\u0629\nconst all = $input.all();\nconst sent = [], failed = [];\nlet batchId = '';\n\nfor (const it of all) {\n const j = it.json;\n batchId = j.batchId || batchId;\n if (j.ok) sent.push({ index: j.index, phone: j.phone, name: j.name, msgId: j.msgId });\n else failed.push({ index: j.index, phone: j.phone, name: j.name, err: j.err });\n}\n\nconst failRate = all.length ? (failed.length / all.length * 100) : 0;\nlet warning = '';\nif (failRate === 100) warning = '\ud83d\udd34 \u0641\u0634\u0644 \u0625\u0631\u0633\u0627\u0644 \u062c\u0645\u064a\u0639 \u0627\u0644\u0631\u0633\u0627\u0626\u0644! \u062a\u062d\u0642\u0642 \u0645\u0646 WhatsApp Credential';\nelse if (failRate > 50) warning = '\ud83d\udfe1 \u0646\u0633\u0628\u0629 \u0641\u0634\u0644 \u0639\u0627\u0644\u064a\u0629! \u062a\u062d\u0642\u0642 \u0645\u0646 \u062a\u0641\u0627\u0635\u064a\u0644 \u0627\u0644\u0623\u062e\u0637\u0627\u0621';\nelse if (failRate > 20) warning = '\ud83d\udfe0 \u0628\u0639\u0636 \u0627\u0644\u0631\u0633\u0627\u0626\u0644 \u0641\u0634\u0644\u062a - \u0631\u0627\u062c\u0639 \u0627\u0644\u062a\u0641\u0627\u0635\u064a\u0644';\n\nconst failedSummary = failed.slice(0, 10).map(f => f.phone + ':' + (f.err || 'err').substring(0, 30)).join(' | ');\n\nreturn [{ json: {\n success: sent.length > 0,\n warning: warning,\n batchId: batchId,\n total: all.length,\n sent: sent.length,\n failed: failed.length,\n rate: all.length ? (sent.length/all.length*100).toFixed(1)+'%' : '0%',\n completedAt: new Date().toISOString(),\n failedSummary: failedSummary,\n details: { sent, failed }\n} }];"
},
"id": "12286b3c-e117-4faf-a00a-3b285b073bce",
"name": "Summary",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
14560,
9600
]
},
{
"parameters": {
"method": "POST",
"url": "=https://firestore.googleapis.com/v1/projects/ramz-alsadara2025/databases/(default)/documents/whatsapp_batch_reports?documentId={{ $json.batchId }}",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleApi",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"fields\": {\n \"batchId\": { \"stringValue\": \"{{ $json.batchId }}\" },\n \"total\": { \"integerValue\": \"{{ $json.total }}\" },\n \"sent\": { \"integerValue\": \"{{ $json.sent }}\" },\n \"failed\": { \"integerValue\": \"{{ $json.failed }}\" },\n \"rate\": { \"stringValue\": \"{{ $json.rate }}\" },\n \"completedAt\": { \"stringValue\": \"{{ $json.completedAt }}\" },\n \"status\": { \"stringValue\": \"{{ $json.earlyStop ? 'stopped' : 'completed' }}\" },\n \"warning\": { \"stringValue\": \"{{ $json.warning || '' }}\" },\n \"failedDetails\": { \"stringValue\": \"{{ $json.failedSummary || '' }}\" },\n \"earlyStop\": { \"booleanValue\": {{ $json.earlyStop || false }} },\n \"earlyStopReason\": { \"stringValue\": \"{{ $json.earlyStopReason || '' }}\" }\n }\n}",
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"id": "dbf43e7a-82f0-499a-bcad-67e14f40df8f",
"name": "SaveBatchReport",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
14720,
9600
],
"credentials": {
"googleApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// \u062a\u0633\u062c\u064a\u0644 \u0627\u0644\u0646\u062a\u064a\u062c\u0629 \u0627\u0644\u0646\u0647\u0627\u0626\u064a\u0629\nconst result = $('Summary').first().json;\n\nconsole.log('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');\nconsole.log(' WhatsApp Templates - Secure Credentials');\nconsole.log('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');\nconsole.log('Batch ID:', result.batchId);\nconsole.log('Total:', result.total);\nconsole.log('Sent:', result.sent);\nconsole.log('Failed:', result.failed);\nconsole.log('Success Rate:', result.rate);\nif (result.warning) console.log('\u26a0\ufe0f Warning:', result.warning);\nconsole.log('\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');\n\nreturn [{ json: result }];"
},
"id": "ffbc33b9-6ef5-468a-9d29-0928f8860c60",
"name": "LogFinalResult",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
14880,
9600
]
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "PrepareData",
"type": "main",
"index": 0
}
]
]
},
"PrepareData": {
"main": [
[
{
"node": "PrepareImmediateResponse",
"type": "main",
"index": 0
}
]
]
},
"PrepareImmediateResponse": {
"main": [
[
{
"node": "RespondImmediately",
"type": "main",
"index": 0
},
{
"node": "CheckHasError",
"type": "main",
"index": 0
}
]
]
},
"CheckHasError": {
"main": [
[
{
"node": "ExtractItems",
"type": "main",
"index": 0
}
]
]
},
"ExtractItems": {
"main": [
[
{
"node": "SplitBatches",
"type": "main",
"index": 0
}
]
]
},
"SplitBatches": {
"main": [
[
{
"node": "Summary",
"type": "main",
"index": 0
}
],
[
{
"node": "CheckPhone",
"type": "main",
"index": 0
}
]
]
},
"CheckPhone": {
"main": [
[
{
"node": "BuildTemplate",
"type": "main",
"index": 0
}
],
[
{
"node": "InvalidPhoneResult",
"type": "main",
"index": 0
}
]
]
},
"BuildTemplate": {
"main": [
[
{
"node": "SendWhatsApp",
"type": "main",
"index": 0
}
]
]
},
"SendWhatsApp": {
"main": [
[
{
"node": "ProcessResult",
"type": "main",
"index": 0
}
]
]
},
"ProcessResult": {
"main": [
[
{
"node": "CheckAuthError",
"type": "main",
"index": 0
}
]
]
},
"CheckAuthError": {
"main": [
[
{
"node": "AuthErrorStop",
"type": "main",
"index": 0
}
],
[
{
"node": "CheckShouldRetry",
"type": "main",
"index": 0
}
]
]
},
"AuthErrorStop": {
"main": [
[
{
"node": "SaveBatchReport",
"type": "main",
"index": 0
}
]
]
},
"CheckShouldRetry": {
"main": [
[
{
"node": "RetryWait",
"type": "main",
"index": 0
}
],
[
{
"node": "CheckSuccess",
"type": "main",
"index": 0
}
]
]
},
"RetryWait": {
"main": [
[
{
"node": "PrepareRetry",
"type": "main",
"index": 0
}
]
]
},
"PrepareRetry": {
"main": [
[
{
"node": "SendWhatsApp",
"type": "main",
"index": 0
}
]
]
},
"CheckSuccess": {
"main": [
[
{
"node": "SaveMessage",
"type": "main",
"index": 0
}
],
[
{
"node": "SendResultFailed",
"type": "main",
"index": 0
}
]
]
},
"SaveMessage": {
"main": [
[
{
"node": "UpdateConversation",
"type": "main",
"index": 0
}
]
]
},
"UpdateConversation": {
"main": [
[
{
"node": "Wait",
"type": "main",
"index": 0
}
]
]
},
"Wait": {
"main": [
[
{
"node": "SendResultSuccess",
"type": "main",
"index": 0
}
]
]
},
"SendResultSuccess": {
"main": [
[
{
"node": "SplitBatches",
"type": "main",
"index": 0
}
]
]
},
"SendResultFailed": {
"main": [
[
{
"node": "SplitBatches",
"type": "main",
"index": 0
}
]
]
},
"InvalidPhoneResult": {
"main": [
[
{
"node": "SplitBatches",
"type": "main",
"index": 0
}
]
]
},
"Summary": {
"main": [
[
{
"node": "SaveBatchReport",
"type": "main",
"index": 0
}
]
]
},
"SaveBatchReport": {
"main": [
[
{
"node": "LogFinalResult",
"type": "main",
"index": 0
}
]
]
}
}
}
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.
googleApiwhatsAppApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
WhatsApp Templates - Secure Credentials. Uses httpRequest. Webhook trigger; 26 nodes.
Source: https://github.com/07706228120Hh/alsadara-FTTH/blob/9c24cd1050b4d40aded0c18f2d72a511739a05b6/n8n-workflows/n8n-whatsapp-templates-CREDENTIALS.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.
HR teams, IT Operations, and System Administrators managing employee onboarding at scale. It’s perfect if you use Odoo 18 to trigger account requests and need Redmine + GitLab accounts created instant
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
qualiopi. Uses airtable, telegram, emailSend, httpRequest. Webhook trigger; 51 nodes.
This workflow automates end-to-end research analysis by coordinating multiple AI models—including NVIDIA NIM (Llama), OpenAI GPT-4, and Claude to analyze uploaded documents, extract insights, and gene
PsyCardv2. Uses executeCommand, telegram, readBinaryFile, googleDrive. Webhook trigger; 41 nodes.