{
  "name": "WAIX · Форма сайта → WhatsApp",
  "nodes": [
    {
      "id": "7a00bd1a-201a-43bb-83ad-b20a2bfc684f",
      "name": "Server webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "waix-website-form",
        "authentication": "headerAuth",
        "responseMode": "responseNode",
        "options": {}
      },
      "webhookId": "5d314e27-5a4b-4025-9561-11f48495d6a1"
    },
    {
      "id": "19ba2d92-4bdd-471d-bb16-49c27fd75509",
      "name": "Prepare request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        0
      ],
      "parameters": {
        "jsCode": "const b = $json.body;\nconst invalid = (error, status = 400, code = 'INVALID_INPUT') => [{json:{valid:false,error,status,code}}];\nconst uuid = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-8][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;\nif (!b || typeof b !== 'object' || Array.isArray(b)) return invalid('Передайте JSON-объект.');\nconst validId = value => typeof value === 'string' && uuid.test(value);\nif (!validId(b.event_id)) return invalid('event_id — UUID, который ваш сервер сохранил для этой операции.');\nif (typeof b.phone !== 'string' || !/^\\+[1-9][0-9]{7,14}$/.test(b.phone)) return invalid('Укажите телефон в формате E.164, например +77071234567.');\nconst connectionId = $json.waix_connection_id;\nconst templateName = $json.waix_template;\nconst language = $json.waix_language;\nif (!validId(connectionId) || typeof templateName !== 'string' || templateName === 'your_approved_template' || !/^[a-z0-9_]{1,512}$/.test(templateName) || typeof language !== 'string' || !/^[a-z]{2,3}(?:_[A-Z]{2})?$/.test(language)) return invalid('Укажите Connection ID, одобренный шаблон и язык в узле WAIX settings.', 500, 'WORKFLOW_NOT_CONFIGURED');\nif (b.whatsapp_consent !== true) return invalid('Нужно подтверждённое согласие клиента на сообщения WhatsApp.');\nif (typeof b.name !== 'string' || !b.name.trim() || b.name.length > 500) return invalid('Заполните name: строка до 500 символов.');\nreturn [{json:{valid:true,idempotency_key:b.event_id,body:{connection_id:connectionId,to:b.phone,type:'template',template:{name:templateName,language:{code:language},components:[{type:'body',parameters:[{type:'text',text:b.name.trim()}]}]}}}}];"
      }
    },
    {
      "id": "4b2083cf-2592-4d35-81b1-ad356041ae2e",
      "name": "Valid request",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        700,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "7a56fdf1-9551-4ed8-b124-60902bbc76df",
              "leftValue": "={{ $json.valid }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "2c3fe89c-6a1b-4199-b8bb-dbf963342f3a",
      "name": "WAIX API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        940,
        -100
      ],
      "parameters": {
        "method": "POST",
        "url": "https://waix.kz/api/v1/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Idempotency-Key",
              "value": "={{ $json.idempotency_key }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.body) }}",
        "options": {
          "timeout": 30000,
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "text",
              "outputPropertyName": "body"
            }
          }
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "id": "e045268c-9c50-42ee-9558-a52453e5f1ed",
      "name": "Return API result",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [
        1420,
        -100
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json.body }}",
        "options": {
          "responseCode": "={{ $json.responseCode }}",
          "responseHeaders": {
            "entries": [
              {
                "name": "Cache-Control",
                "value": "no-store"
              },
              {
                "name": "={{ $json.responseHeaders.some(h => h.name === 'X-Request-Id') ? 'X-Request-Id' : 'Cache-Control' }}",
                "value": "={{ $json.responseHeaders.find(h => h.name === 'X-Request-Id')?.value || 'no-store' }}"
              },
              {
                "name": "={{ $json.responseHeaders.some(h => h.name === 'Retry-After') ? 'Retry-After' : 'Cache-Control' }}",
                "value": "={{ $json.responseHeaders.find(h => h.name === 'Retry-After')?.value || 'no-store' }}"
              }
            ]
          }
        }
      }
    },
    {
      "id": "a1f4db55-fa2c-4000-b711-4479f50777c8",
      "name": "Invalid request",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [
        940,
        150
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ {error:$json.error,code:$json.code} }}",
        "options": {
          "responseCode": "={{ $json.status }}",
          "responseHeaders": {
            "entries": [
              {
                "name": "Cache-Control",
                "value": "no-store"
              }
            ]
          }
        }
      }
    },
    {
      "id": "c04afd6b-29c9-4760-b81a-c88347b4dada",
      "name": "Setup",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -320
      ],
      "parameters": {
        "width": 1500,
        "height": 250,
        "content": "## WAIX · Форма сайта → WhatsApp\n1. Server webhook: Header Auth, X-Workflow-Key = ваш секрет. Только для вызова с backend.\n2. WAIX API: отдельный Header Auth, Authorization = Bearer <ключ WAIX>.\n3. WAIX settings: задайте Connection ID, шаблон и язык; для OTP — срок кода. OTP использует ключ проекта.\n4. event_id сохраняется до первого запроса. При сетевой ошибке — то же тело и тот же UUID.\n5. История и ручные данные отключены. Ответы не содержат sandbox test_code.\n6. 202 — очередь, а не доставка. 400 — проверьте поля, 401/403 — ключ и права, 429 — дождитесь Retry-After.\nИнструкция: https://github.com/ivanpukhov/waix-integrations/blob/main/n8n/website-form.README.md"
      }
    },
    {
      "id": "955ea5ca-f7da-4f61-9acd-79afb3f1f91e",
      "name": "WAIX settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 2,
      "position": [
        220,
        0
      ],
      "parameters": {
        "keepOnlySet": false,
        "values": {
          "string": [
            {
              "name": "waix_connection_id",
              "value": "REPLACE_CONNECTION_ID"
            },
            {
              "name": "waix_template",
              "value": "your_approved_template"
            },
            {
              "name": "waix_language",
              "value": "ru"
            }
          ],
          "number": []
        },
        "options": {}
      }
    },
    {
      "id": "26a4e157-c69e-4592-8846-99c5838273fa",
      "name": "Normalize response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1180,
        -100
      ],
      "parameters": {
        "jsCode": "// HTTP Request возвращает текст: даже HTML от прокси станет понятной HTTP-ошибкой.\nconst status = Number($json.statusCode);\nconst headers = $json.headers || {};\nconst requestId = headers['x-request-id'] || headers['X-Request-Id'];\nconst retryAfter = headers['retry-after'] || headers['Retry-After'];\nconst meta = { request_id: typeof requestId === 'string' ? requestId : null };\nlet body = $json.body;\ntry { if (typeof body === 'string') body = JSON.parse(body); } catch { body = null; }\nconst object = body && typeof body === 'object' && !Array.isArray(body);\nlet responseCode = Number.isInteger(status) && status >= 400 && status <= 599 ? status : 502;\nif (status >= 200 && status < 300 && object) responseCode = status;\nif (!object) body = {error: 'WAIX вернул ответ, который не удалось прочитать.', code: 'INVALID_RESPONSE', ...meta};\nif (status >= 300 && status < 400) body = {error: 'WAIX вернул перенаправление. Проверьте адрес API.', code: 'REDIRECT_DISALLOWED', ...meta};\nif (body.data && typeof body.data === 'object' && !Array.isArray(body.data)) {\n  const {test_code, ...safe} = body.data;\n  body = {...body, data: safe};\n}\nconst responseHeaders = [{name: 'Cache-Control', value: 'no-store'}];\nif (typeof requestId === 'string' && /^[A-Za-z0-9_.:-]{1,160}$/.test(requestId)) responseHeaders.push({name:'X-Request-Id',value:requestId});\nif (typeof retryAfter === 'string' && (/^[0-9]{1,8}$/.test(retryAfter) || /^[A-Za-z]{3}, [0-9]{2} [A-Za-z]{3} [0-9]{4} [0-9:]{8} GMT$/.test(retryAfter))) responseHeaders.push({name:'Retry-After',value:retryAfter});\nreturn [{json:{body,responseCode,responseHeaders}}];"
      }
    },
    {
      "id": "33657e1f-e935-4518-af25-34524c76de0a",
      "name": "Transport error",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [
        1180,
        160
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ {error:'Не удалось получить ответ WAIX. Для отправки результат неизвестен; повторите прежнее тело с тем же event_id. Не создавайте новый UUID.',code:'UPSTREAM_UNAVAILABLE',outcome_unknown:true} }}",
        "options": {
          "responseCode": 502,
          "responseHeaders": {
            "entries": [
              {
                "name": "Cache-Control",
                "value": "no-store"
              }
            ]
          }
        }
      }
    }
  ],
  "connections": {
    "Server webhook": {
      "main": [
        [
          {
            "node": "WAIX settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare request": {
      "main": [
        [
          {
            "node": "Valid request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Valid request": {
      "main": [
        [
          {
            "node": "WAIX API",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Invalid request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "WAIX API": {
      "main": [
        [
          {
            "node": "Normalize response",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Transport error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "WAIX settings": {
      "main": [
        [
          {
            "node": "Prepare request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize response": {
      "main": [
        [
          {
            "node": "Return API result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "saveDataErrorExecution": "none",
    "saveDataSuccessExecution": "none",
    "saveExecutionProgress": false,
    "saveManualExecutions": false
  },
  "pinData": {},
  "tags": []
}
