{
  "name": "Handle foreign-language LINE inquiries in Japanese: Claude translates both ways, with a back-check and a number guard",
  "nodes": [
    {
      "id": "6049302c-3dfb-444f-94f1-f03e9fc1ebb8",
      "name": "Slack message",
      "type": "n8n-nodes-base.slackTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "parameters": {
        "trigger": [
          "message"
        ],
        "watchWorkspace": false,
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "options": {}
      }
    },
    {
      "id": "83c3557d-52c3-4599-84da-679a78f22527",
      "name": "Translation settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        0
      ],
      "parameters": {
        "mode": "manual",
        "includeOtherFields": true,
        "assignments": {
          "assignments": [
            {
              "id": "t1",
              "name": "tone",
              "value": "polite, friendly and concise",
              "type": "string"
            },
            {
              "id": "t2",
              "name": "glossary",
              "value": "来店予約 = en: visit reservation; zh-Hans: 到店预约; zh-Hant: 到店預約; ko: 방문 예약\nさくら家具 = en: Sakura Furniture; zh-Hans: 樱花家具; zh-Hant: 櫻花家具; ko: 사쿠라 가구",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "id": "af1076ca-e8ac-495c-b7d8-4bcbf1a53831",
      "name": "Staff thread replies only",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.3,
      "position": [
        440,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "leftValue": "={{ $json.thread_ts }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            },
            {
              "leftValue": "={{ $json.thread_ts }}",
              "rightValue": "={{ $json.ts }}",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            },
            {
              "leftValue": "={{ $json.bot_id ?? \"\" }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              }
            },
            {
              "leftValue": "={{ ($json.text ?? \"\").trim() }}",
              "rightValue": "//",
              "operator": {
                "type": "string",
                "operation": "notStartsWith"
              }
            },
            {
              "leftValue": "={{ [\"\", \"thread_broadcast\"].includes($json.subtype ?? \"\") }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "6ea76333-8354-493e-aab5-cb435307a5bd",
      "name": "Find customer",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        660,
        0
      ],
      "parameters": {
        "resource": "row",
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "slack_ts",
              "condition": "eq",
              "keyValue": "={{ $json.thread_ts }}"
            }
          ]
        },
        "limit": 1
      }
    },
    {
      "id": "f0365475-1fdf-4261-9198-601c3cf885cb",
      "name": "Prepare reply",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        0
      ],
      "parameters": {
        "jsCode": "function slackToPlain(t) {\n  return String(t ?? '')\n    .replace(/<(https?:[^|>]+)\\|[^>]*>/g, '$1')\n    .replace(/<(https?:[^>]+)>/g, '$1')\n    .replace(/<mailto:([^|>]+)\\|[^>]*>/g, '$1')\n    .replace(/<@[A-Z0-9]+(?:\\|[^>]*)?>/g, '')\n    .replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&')\n    .trim();\n}\n\n// One place that turns the Slack reply into the Japanese text, the customer's language, and the data the translation needs.\nconst row = $input.first().json;\nconst msg = $('Slack message').first().json;\nconst settings = $('Translation settings').first().json;\nconst ja_text = slackToPlain(msg.text);\nif (!ja_text) return [];\nconst language = row.language || 'ja';\nreturn [{ json: {\n  line_user_id: row.line_user_id,\n  language,\n  language_name: row.language_name || 'Japanese',\n  needs_translation: language !== 'ja',\n  ja_text,\n  tone: settings.tone,\n  glossary: settings.glossary,\n  thread_ts: msg.thread_ts,\n  reply_ts: msg.ts,\n} }];"
      }
    },
    {
      "id": "cdc1f399-6684-47ef-aac4-807728728ea9",
      "name": "Needs translation?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1100,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "m1",
              "leftValue": "={{ $json.needs_translation }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "70af9b7d-73ee-4bad-a147-919447d8abc5",
      "name": "Send as written",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1100,
        220
      ],
      "parameters": {
        "mode": "manual",
        "includeOtherFields": false,
        "assignments": {
          "assignments": [
            {
              "id": "s1",
              "name": "send_text",
              "value": "={{ $json.ja_text }}",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "id": "9d806d8b-964e-4fa7-8b5d-949e25c4eddc",
      "name": "AI: Translate",
      "type": "@n8n/n8n-nodes-langchain.anthropic",
      "typeVersion": 1,
      "position": [
        1350,
        -100
      ],
      "parameters": {
        "resource": "text",
        "operation": "message",
        "modelId": {
          "__rl": true,
          "mode": "id",
          "value": "claude-haiku-4-5-20251001"
        },
        "messages": {
          "values": [
            {
              "content": "=You are an interpreter for a shop in Japan. Translate the staff message below from Japanese into {{ $json.language_name }} (language code {{ $json.language }}). The customer reads it in a LINE chat.\n\n# Rules\n- Keep every number, date, time, amount, URL, email address and phone number exactly as written in the Japanese message. Do not convert units, currencies or time formats (keep 15:00 as 15:00, keep 1,200 as 1,200).\n- Dates: keep the month and the day as digits. In a language that writes the month as a name, write both, for example 3月4日 -> \"3/4 (March 4)\".\n- Fixed translations for shop terms. Each line is a Japanese term followed by translations by language code; when the line has one for the target language, use exactly that, otherwise translate naturally:\n{{ $json.glossary || \"(none)\" }}\n- Tone: {{ $json.tone }}.\n- Do not add or remove information. Do not add greetings, explanations or notes.\n- back_translation: translate YOUR translation back into Japanese, literally, as if you had not seen the original. It is shown to the staff so that they can notice mistakes.\n- Output JSON only (no explanation, no code block).\n\n# Output schema\n{\"translation\": \"message in {{ $json.language_name }}\", \"back_translation\": \"literal Japanese of your translation\"}\n\n# Japanese message\n{{ $json.ja_text }}",
              "role": "user"
            }
          ]
        },
        "simplify": true,
        "options": {
          "maxTokens": 1500,
          "temperature": 0
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "3916dcaf-3a1d-48ff-b348-7be3d4448571",
      "name": "Check translation",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1658,
        -100
      ],
      "parameters": {
        "jsCode": "// Checks that numbers, URLs and email addresses of the Japanese message survive in the translation.\n// It looks at the digits as written, so it errs on the side of stopping (for example \"15:00\" -> \"3 PM\" is stopped).\nfunction checkTranslation(ja, tr) {\n  const nfkc = (s) => String(s ?? '').normalize('NFKC');\n  const src = nfkc(ja);\n  const dst = nfkc(tr);\n  const problems = [];\n  const nums = (s) => s.replace(/(?<=\\d),(?=\\d{3}(?!\\d))/g, '').match(/\\d+/g) ?? [];\n  const count = (arr) => arr.reduce((m, x) => ((m[x] = (m[x] || 0) + 1), m), {});\n  const cs = count(nums(src));\n  const cd = count(nums(dst));\n  const missing = Object.keys(cs).filter((k) => (cd[k] || 0) < cs[k]);\n  if (missing.length) problems.push('numbers missing in the translation: ' + missing.join(', '));\n  const trimEnd = (u) => u.replace(/[)\\]、。，．,.!?！？」』）]+$/g, '');\n  for (const re of [/https?:\\/\\/[^\\s<>\"]+/g, /[\\w.+-]+@[\\w-]+(?:\\.[\\w-]+)+/g]) {\n    for (const m of src.match(re) ?? []) {\n      const u = trimEnd(m);\n      if (!dst.includes(u)) problems.push('missing in the translation: ' + u);\n    }\n  }\n  if (!dst.trim()) problems.push('the translation is empty');\n  const kana = (dst.match(/[぀-ヿ]/g) ?? []).length;\n  if (kana > dst.length * 0.3) problems.push('the translation still looks Japanese');\n  return problems;\n}\n\nconst base = $('Prepare reply').first().json;\nlet translation = '', reason = '';\ntry {\n  const rawText = $json.content?.[0]?.text ?? $json.text;\n  // Claude has no JSON mode, so it may wrap the JSON in a code fence. Strip it before parsing.\n  const t = String(rawText ?? '').replace(/^\\s*```(?:json)?\\s*/i, '').replace(/\\s*```\\s*$/, '').trim();\n  translation = String(JSON.parse(t).translation ?? '').trim();\n} catch (e) { reason = 'The AI output could not be read'; }\nif (!reason) {\n  const problems = checkTranslation(base.ja_text, translation);\n  if (problems.length) reason = problems.join('; ');\n}\nreturn [{ json: { ...base, translation, translation_ok: !reason, reason } }];"
      }
    },
    {
      "id": "b4fe2026-9de9-4826-a8ab-a9a8c64c0471",
      "name": "Translation OK?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1966,
        -100
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "m2",
              "leftValue": "={{ $json.translation_ok }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "94ff9dfe-7e2e-43b4-abc7-a29f3b7fe652",
      "name": "Report: not sent",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.7,
      "position": [
        2274,
        -300
      ],
      "parameters": {
        "resource": "message",
        "operation": "post",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "messageType": "text",
        "text": "={{ \"⚠ Not sent to the customer: \" + $json.reason + \"\\n\\nTranslation attempt (\" + $json.language_name + \"):\\n\" + ($json.translation || \"(none)\") + \"\\n\\nPlease reword the message (for example write times and amounts with digits) and send it again.\" }}",
        "otherOptions": {
          "includeLinkToWorkflow": false,
          "thread_ts": {
            "replyValues": {
              "thread_ts": "={{ $json.thread_ts }}"
            }
          }
        }
      }
    },
    {
      "id": "5cc1ab93-0e22-4149-875f-7a6dfe682b7e",
      "name": "AI: Back-translate (blind)",
      "type": "@n8n/n8n-nodes-langchain.anthropic",
      "typeVersion": 1,
      "position": [
        2274,
        -100
      ],
      "parameters": {
        "resource": "text",
        "operation": "message",
        "modelId": {
          "__rl": true,
          "mode": "id",
          "value": "claude-haiku-4-5-20251001"
        },
        "messages": {
          "values": [
            {
              "content": "=Translate the following message into Japanese, literally and completely. Output only the Japanese text.\n\n{{ $json.translation }}",
              "role": "user"
            }
          ]
        },
        "simplify": true,
        "options": {
          "maxTokens": 800,
          "temperature": 0
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "d409b3fd-370d-418e-a5e5-80142660b44a",
      "name": "Prepare translated send",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2582,
        -100
      ],
      "parameters": {
        "jsCode": "const base = $('Check translation').first().json;\n// The back-translation comes from a second call that never saw the Japanese original, so it cannot just copy it.\nconst back = String($json.content?.[0]?.text ?? $json.text ?? '').trim() || '(back-translation unavailable)';\nreturn [{ json: { ...base, send_text: base.translation, back_translation: back } }];"
      }
    },
    {
      "id": "c3781ddc-fe1d-4f8c-bfc1-71b53e6d8678",
      "name": "Send to LINE",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        2832,
        0
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.line.me/v2/bot/message/push",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ to: $(\"Prepare reply\").first().json.line_user_id, messages: [{ type: \"text\", text: $json.send_text }] }) }}",
        "options": {}
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "94c1835b-5d30-4930-9781-91faf46ca9fc",
      "name": "Sent?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3052,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "leftValue": "={{ $json.error ? \"ng\" : \"ok\" }}",
              "rightValue": "ok",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "31ff7741-ddb0-4640-8a13-ef7d9f750fc2",
      "name": "Mark as sent",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.7,
      "position": [
        3272,
        -100
      ],
      "parameters": {
        "resource": "reaction",
        "operation": "add",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "timestamp": "={{ $(\"Prepare reply\").first().json.reply_ts }}",
        "name": "white_check_mark"
      }
    },
    {
      "id": "a4088f4f-7225-48d8-a7cb-946be2c39e8a",
      "name": "Mark thread replied",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        3492,
        -100
      ],
      "parameters": {
        "resource": "row",
        "operation": "update",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "slack_ts",
              "condition": "eq",
              "keyValue": "={{ $(\"Prepare reply\").first().json.thread_ts }}"
            }
          ]
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "status": "replied"
          },
          "schema": [
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        }
      }
    },
    {
      "id": "e8bef657-d05a-471e-8bae-8b3323e13c31",
      "name": "Was it translated?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3712,
        -100
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "m3",
              "leftValue": "={{ $(\"Prepare reply\").first().json.needs_translation }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "849ef748-20d8-49ad-966d-148da41f0047",
      "name": "Show what was sent",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.7,
      "position": [
        3932,
        -200
      ],
      "parameters": {
        "resource": "message",
        "operation": "post",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "messageType": "text",
        "text": "={{ \"📤 Sent in \" + $(\"Prepare translated send\").first().json.language_name + \":\\n\" + $(\"Prepare translated send\").first().json.translation + \"\\n\\n↩ Back in Japanese (translated separately, without the original):\\n\" + $(\"Prepare translated send\").first().json.back_translation }}",
        "otherOptions": {
          "includeLinkToWorkflow": false,
          "thread_ts": {
            "replyValues": {
              "thread_ts": "={{ $(\"Prepare reply\").first().json.thread_ts }}"
            }
          }
        }
      }
    },
    {
      "id": "8c7af4d7-3085-4b63-97e0-224b40dde8b7",
      "name": "Report failure",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.7,
      "position": [
        3272,
        120
      ],
      "parameters": {
        "resource": "message",
        "operation": "post",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "messageType": "text",
        "text": "={{ \"⚠ This reply could not be sent to LINE: \" + JSON.stringify($json.error?.message ?? $json.error) }}",
        "otherOptions": {
          "includeLinkToWorkflow": false,
          "thread_ts": {
            "replyValues": {
              "thread_ts": "={{ $(\"Prepare reply\").first().json.thread_ts }}"
            }
          }
        }
      }
    },
    {
      "id": "d468036e-813d-482a-b298-018698831954",
      "name": "Receive LINE webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        0,
        -900
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "multilingual-desk",
        "responseMode": "onReceived",
        "options": {
          "rawBody": true
        }
      }
    },
    {
      "id": "97757c47-fe15-49a0-8d06-39b9e3294c9b",
      "name": "Compute signature",
      "type": "n8n-nodes-base.crypto",
      "typeVersion": 2,
      "position": [
        220,
        -900
      ],
      "parameters": {
        "action": "hmac",
        "binaryData": true,
        "binaryPropertyName": "data",
        "type": "SHA256",
        "encoding": "base64",
        "dataPropertyName": "computedSignature"
      }
    },
    {
      "id": "da941747-254a-4f1e-a819-329356f2fe7c",
      "name": "Signature valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        440,
        -900
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "leftValue": "={{ $json.computedSignature }}",
              "rightValue": "={{ $json.headers['x-line-signature'] }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "1c71603d-9612-44f7-a948-b46370fd3b38",
      "name": "Drop invalid request",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        660,
        -780
      ],
      "parameters": {}
    },
    {
      "id": "bc655943-c600-4fb7-9fc8-4dda8e1bb601",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        660,
        -1100
      ],
      "parameters": {
        "mode": "manual",
        "includeOtherFields": true,
        "assignments": {
          "assignments": [
            {
              "id": "c1",
              "name": "acknowledgment_ja",
              "value": "お問い合わせありがとうございます。担当者が確認して、このチャットでお返事します。",
              "type": "string"
            },
            {
              "id": "c2",
              "name": "low_confidence",
              "value": 0.6,
              "type": "number"
            },
            {
              "id": "c3",
              "name": "acknowledgment_en",
              "value": "Thank you for your message. A staff member will check it and reply in this chat.",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "id": "c680c50e-0c50-44d3-ba1a-8ff898f076f7",
      "name": "Split events",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        880,
        -1100
      ],
      "parameters": {
        "fieldToSplitOut": "body.events"
      }
    },
    {
      "id": "c6ebaf33-6032-4e9c-b31b-1a3888e18e71",
      "name": "Skip redelivered events",
      "type": "n8n-nodes-base.removeDuplicates",
      "typeVersion": 2,
      "position": [
        1100,
        -1100
      ],
      "parameters": {
        "operation": "removeItemsSeenInPreviousExecutions",
        "logic": "removeItemsWithAlreadySeenKeyValues",
        "dedupeValue": "={{ $json.webhookEventId }}"
      }
    },
    {
      "id": "f3c169ac-50fe-4106-87ac-6547c3e46bbe",
      "name": "Extract message fields",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1320,
        -1100
      ],
      "parameters": {
        "mode": "manual",
        "includeOtherFields": false,
        "assignments": {
          "assignments": [
            {
              "id": "u",
              "name": "userId",
              "value": "={{ $json.source.userId }}",
              "type": "string"
            },
            {
              "id": "r",
              "name": "replyToken",
              "value": "={{ $json.replyToken }}",
              "type": "string"
            },
            {
              "id": "t",
              "name": "messageType",
              "value": "={{ $json.type === 'message' ? $json.message.type : $json.type }}",
              "type": "string"
            },
            {
              "id": "m",
              "name": "messageId",
              "value": "={{ $json.message?.id ?? \"\" }}",
              "type": "string"
            },
            {
              "id": "x",
              "name": "text",
              "value": "={{ $json.message?.text ?? \"\" }}",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "id": "5e127a59-ca6a-4a72-a84a-3a3c16063c7d",
      "name": "Text messages only",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.3,
      "position": [
        1540,
        -1100
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "leftValue": "={{ $json.messageType }}",
              "rightValue": "text",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "2cfcefe0-7ecd-4b4c-86e1-afa319b2b801",
      "name": "Process one event at a time",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        1760,
        -1100
      ],
      "parameters": {
        "batchSize": 1,
        "options": {}
      }
    },
    {
      "id": "75252c7e-2663-434b-919f-1f189220d2df",
      "name": "Current event",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2010,
        -1100
      ],
      "parameters": {
        "jsCode": "// Everything below reads the event from here: inside the loop this node has exactly one item.\nreturn [{ json: $input.first().json }];"
      }
    },
    {
      "id": "26291c11-4964-46aa-bd22-5a532ec750dd",
      "name": "Find thread",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        2318,
        -1100
      ],
      "alwaysOutputData": true,
      "parameters": {
        "resource": "row",
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "line_user_id",
              "condition": "eq",
              "keyValue": "={{ $(\"Current event\").first().json.userId }}"
            }
          ]
        },
        "limit": 1
      }
    },
    {
      "id": "f5fdead4-280f-40fb-a1b7-0b3210f7234c",
      "name": "AI: Detect language and translate",
      "type": "@n8n/n8n-nodes-langchain.anthropic",
      "typeVersion": 1,
      "position": [
        2626,
        -1100
      ],
      "parameters": {
        "resource": "text",
        "operation": "message",
        "modelId": {
          "__rl": true,
          "mode": "id",
          "value": "claude-haiku-4-5-20251001"
        },
        "messages": {
          "values": [
            {
              "content": "=You help a shop in Japan answer customers who write on LINE. Read the customer's message and return JSON. Do not answer the customer's question.\n\n# Tasks\n1. language: the language code of the message (ja, en, zh-Hans for Simplified Chinese, zh-Hant for Traditional Chinese, ko, th, vi, id, es, fr, ...).\n2. language_name: the English name of that language.\n3. confidence: a number from 0 to 1. If the message is too short or has no words to tell the language (OK, ?, numbers, an emoji, a name) and the customer wrote in a language before, answer with that previous language and a confidence of 0.9. If there is no previous language, give a low confidence.\n4. ja: the message translated into Japanese. If it is already Japanese, or has no words to translate (OK, an emoji, numbers, a name), copy it unchanged. If you cannot understand it, use an empty string.\n5. ack: this acknowledgment translated into the language of task 1 (copy it unchanged if that language is Japanese): {{ $('Config').first().json.acknowledgment_ja }}\n\n# Rules\n- In ja, keep numbers, dates, times, amounts, URLs, email addresses and names of people and places as written.\n- Do not add anything and do not leave anything out.\n- Output JSON only (no explanation, no code block).\n\n# Output schema\n{\"language\": \"code\", \"language_name\": \"English name\", \"confidence\": 0.0, \"ja\": \"Japanese translation\", \"ack\": \"acknowledgment in the customer's language\"}\n\n# Language this customer wrote in before\n{{ $('Find thread').first().json.language_name || '(none)' }}\n\n# Customer message\n{{ $('Current event').first().json.text }}",
              "role": "user"
            }
          ]
        },
        "simplify": true,
        "options": {
          "maxTokens": 1500,
          "temperature": 0
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "6ab5b05f-7653-4c5b-9dc6-9ef2b869c79a",
      "name": "Check detection",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2934,
        -1100
      ],
      "parameters": {
        "jsCode": "const ev = $('Current event').first().json;\nconst row = $('Find thread').first().json;\nconst cfg = $('Config').first().json;\nconst prev = { code: row.language || '', name: row.language_name || '' };\nlet o = {};\ntry {\n  const rawText = $json.content?.[0]?.text ?? $json.text;\n  // Claude has no JSON mode, so it may wrap the JSON in a code fence. Strip it before parsing.\n  const t = String(rawText ?? '').replace(/^\\s*```(?:json)?\\s*/i, '').replace(/\\s*```\\s*$/, '').trim();\n  o = JSON.parse(t);\n} catch (e) { o = {}; }\nconst confidence = Number(o.confidence);\nlet code = String(o.language || '').trim();\nlet name = String(o.language_name || '').trim();\n// A short or unclear message (\"OK\", \"?\", an emoji) must not change the language we already know.\nlet uncertain = false;\nif (!code || !(confidence >= cfg.low_confidence)) {\n  if (prev.code) { code = prev.code; name = prev.name; }\n  else { uncertain = true; code = ''; name = ''; }\n}\nconst original = String(ev.text ?? '').trim();\nconst ja = String(o.ja ?? '').trim() || (code === 'ja' ? original : '(could not be translated)');\nconst translated = !!String(o.ja ?? '').trim() || code === 'ja';\nlet ack = String(o.ack ?? '').trim() || cfg.acknowledgment_ja;\n// If the language is unknown, answer in Japanese and English.\nif (uncertain) ack = cfg.acknowledgment_ja + '\\n\\n' + cfg.acknowledgment_en;\nconst lines = [];\nif (uncertain) lines.push('❓ The language could not be detected. Original text and a Japanese attempt:');\nelse if (code !== 'ja') lines.push('🌐 ' + name + ' (' + code + ')');\nlines.push('📩 ' + original);\nif (code !== 'ja') lines.push('🇯🇵 ' + ja);\nreturn [{ json: {\n  language: code, language_name: name, uncertain, ja_text: ja, original, ack_text: ack,\n  store_inquiry: translated ? ja : String(row.last_inquiry ?? ''),\n  slack_text: lines.join(\"\\n\"),\n  store_language: uncertain ? (prev.code || \"\") : code,\n  store_language_name: uncertain ? (prev.name || \"\") : name,\n} }];"
      }
    },
    {
      "id": "112eea05-6ad5-4b54-8754-2ea6e9e84d58",
      "name": "Send acknowledgment",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        3184,
        -1100
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.line.me/v2/bot/message/reply",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ replyToken: $(\"Current event\").first().json.replyToken, messages: [{ type: \"text\", text: $(\"Check detection\").first().json.ack_text }] }) }}",
        "options": {}
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "2274cf3f-6397-49c6-8cb4-22041e28d752",
      "name": "Build Slack note",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3426,
        -1100
      ],
      "parameters": {
        "mode": "manual",
        "includeOtherFields": false,
        "assignments": {
          "assignments": [
            {
              "id": "n1",
              "name": "note",
              "value": "={{ $(\"Check detection\").first().json.slack_text + ($json.error ? \"\\n⚠ The acknowledgment could not be sent to LINE.\" : \"\") }}",
              "type": "string"
            }
          ]
        }
      }
    },
    {
      "id": "b820df15-d248-4fe3-afcf-2bbfc38ebadd",
      "name": "Thread exists?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3668,
        -1100
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "a1",
              "leftValue": "={{ !!$(\"Find thread\").first().json.slack_ts }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "287603d9-d433-49b8-9480-232ff5cfa3fc",
      "name": "Reply in thread",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.7,
      "position": [
        3910,
        -1200
      ],
      "parameters": {
        "resource": "message",
        "operation": "post",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "messageType": "text",
        "text": "={{ $json.note }}",
        "otherOptions": {
          "includeLinkToWorkflow": false,
          "thread_ts": {
            "replyValues": {
              "thread_ts": "={{ $(\"Find thread\").first().json.slack_ts }}",
              "reply_broadcast": true
            }
          }
        }
      }
    },
    {
      "id": "22a0ea64-f155-408e-a4a9-fb288f5dd75a",
      "name": "Update thread status",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        4152,
        -1200
      ],
      "parameters": {
        "resource": "row",
        "operation": "update",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "line_user_id",
              "condition": "eq",
              "keyValue": "={{ $(\"Current event\").first().json.userId }}"
            }
          ]
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "status": "needs_reply",
            "last_inquiry": "={{ $(\"Check detection\").first().json.store_inquiry }}",
            "language": "={{ $(\"Check detection\").first().json.store_language }}",
            "language_name": "={{ $(\"Check detection\").first().json.store_language_name }}"
          },
          "schema": [
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "last_inquiry",
              "displayName": "last_inquiry",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "language",
              "displayName": "language",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "language_name",
              "displayName": "language_name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        }
      }
    },
    {
      "id": "3275fb8c-d32f-4c59-b29f-b42c510c326a",
      "name": "Get LINE display name",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        3910,
        -1000
      ],
      "onError": "continueRegularOutput",
      "parameters": {
        "method": "GET",
        "url": "=https://api.line.me/v2/bot/profile/{{ $(\"Current event\").first().json.userId }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      }
    },
    {
      "id": "e6c335f8-f868-4a2d-aae9-b3daf82d2f06",
      "name": "Create new thread",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.7,
      "position": [
        4152,
        -1000
      ],
      "parameters": {
        "resource": "message",
        "operation": "post",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "messageType": "text",
        "text": "={{ \"LINE customer: \" + ($json.displayName || \"(name not available)\") + \"\\n\" + $(\"Build Slack note\").first().json.note }}",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      }
    },
    {
      "id": "2bc19690-5883-4139-be88-5370a2fbc654",
      "name": "Save thread",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        4394,
        -1000
      ],
      "parameters": {
        "resource": "row",
        "operation": "insert",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "line_user_id": "={{ $(\"Current event\").first().json.userId }}",
            "slack_ts": "={{ $json.message_timestamp }}",
            "status": "needs_reply",
            "last_inquiry": "={{ $(\"Check detection\").first().json.store_inquiry }}",
            "language": "={{ $(\"Check detection\").first().json.store_language }}",
            "language_name": "={{ $(\"Check detection\").first().json.store_language_name }}"
          },
          "schema": [
            {
              "id": "line_user_id",
              "displayName": "line_user_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "slack_ts",
              "displayName": "slack_ts",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "last_inquiry",
              "displayName": "last_inquiry",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "language",
              "displayName": "language",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "language_name",
              "displayName": "language_name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        }
      }
    },
    {
      "parameters": {
        "content": "## 1. A LINE message arrives\nChecks the LINE signature, skips redelivered events and keeps text messages only. Each message is handled on its own, so several customers in one call are never mixed up.",
        "height": 610,
        "width": 1960,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -60,
        -1220
      ],
      "id": "sticky-2-c050bfc5",
      "name": "Section 1"
    },
    {
      "parameters": {
        "content": "## 2. Detect the language, translate to Japanese\nOne Claude call returns the language, the Japanese translation and the acknowledgment. A short or unclear message keeps the customer's earlier language; with no earlier language it is flagged.",
        "height": 290,
        "width": 1124,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1950,
        -1220
      ],
      "id": "sticky-3-95d7edca",
      "name": "Section 2"
    },
    {
      "parameters": {
        "content": "## 3. Acknowledge and post to Slack\nReplies on LINE in the customer's language, then posts the original and the Japanese in the customer's Slack thread (new customers get a new thread) and saves the language.",
        "height": 490,
        "width": 1410,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        3124,
        -1320
      ],
      "id": "sticky-4-637bee6d",
      "name": "Section 3"
    },
    {
      "parameters": {
        "content": "## 4. Staff reply in Slack\nOnly human replies in a customer thread count (messages starting with // are notes). Slack links become plain URLs. A Japanese-speaking customer gets the text as written.",
        "height": 510,
        "width": 1300,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -60,
        -120
      ],
      "id": "sticky-5-93f988ca",
      "name": "Section 4"
    },
    {
      "parameters": {
        "content": "## 5. Translate and check\nClaude translates with your glossary. The number guard compares digits, URLs and emails with the Japanese; on a mismatch nothing is sent. The blind back-translation never sees the original.",
        "height": 490,
        "width": 1432,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1290,
        -420
      ],
      "id": "sticky-6-1afcf9d4",
      "name": "Section 5"
    },
    {
      "parameters": {
        "content": "## 6. Send and show what was sent\nSends the text on LINE (a ✅ means it was sent) and posts the translation with its Japanese back-check in the thread. A LINE failure is reported in the thread.",
        "height": 610,
        "width": 1300,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2772,
        -320
      ],
      "id": "sticky-7-0d9c5b09",
      "name": "Section 6"
    },
    {
      "parameters": {
        "content": "## Foreign-language LINE inquiries, answered in Japanese\nCustomers write on LINE in their own language. Your staff read Japanese and answer in Japanese in Slack. Claude translates both ways, and the translation is checked before it is sent.\n\n### How it works\n1. A LINE message arrives. Claude detects the language, translates it into Japanese, and the customer gets an acknowledgment in their language. The original and the Japanese are posted in the customer's Slack thread.\n2. The language is remembered per customer, so \"OK\" or an emoji does not change it.\n3. Staff reply in the thread in Japanese. The reply is translated into the customer's language and sent on LINE.\n4. **Number guard:** every number, URL and email address of the Japanese reply must appear in the translation, or nothing is sent and Slack says why.\n5. **Back-check:** a second Claude call, which never sees the Japanese, translates the sent text back into Japanese. Staff see it in the thread.\n\n### Setup\n1. Create a Data Table `line_slack_threads` with string columns line_user_id, slack_ts, status, last_inquiry, language, language_name. Select it in the Data table nodes.\n2. Credentials: Header Auth for LINE (`Authorization: Bearer <token>`), Crypto (LINE channel secret), Slack API, Anthropic.\n3. Select your Slack channel in every Slack node. Put your shop terms in **Translation settings** (glossary).\n4. Publish, then set the LINE webhook to **Receive LINE webhook** and the Slack Event Subscriptions URL to **Slack message**.\n\n### Customization\nEdit the acknowledgment text and low_confidence in **Config**, and the tone and glossary in **Translation settings**.",
        "height": 800,
        "width": 760,
        "color": 1
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -130,
        -2260
      ],
      "id": "sticky-1-0d943035",
      "name": "Overview"
    }
  ],
  "connections": {
    "Slack message": {
      "main": [
        [
          {
            "node": "Translation settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Translation settings": {
      "main": [
        [
          {
            "node": "Staff thread replies only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Staff thread replies only": {
      "main": [
        [
          {
            "node": "Find customer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find customer": {
      "main": [
        [
          {
            "node": "Prepare reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare reply": {
      "main": [
        [
          {
            "node": "Needs translation?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs translation?": {
      "main": [
        [
          {
            "node": "AI: Translate",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send as written",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI: Translate": {
      "main": [
        [
          {
            "node": "Check translation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check translation": {
      "main": [
        [
          {
            "node": "Translation OK?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Translation OK?": {
      "main": [
        [
          {
            "node": "AI: Back-translate (blind)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Report: not sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI: Back-translate (blind)": {
      "main": [
        [
          {
            "node": "Prepare translated send",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send as written": {
      "main": [
        [
          {
            "node": "Send to LINE",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare translated send": {
      "main": [
        [
          {
            "node": "Send to LINE",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to LINE": {
      "main": [
        [
          {
            "node": "Sent?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sent?": {
      "main": [
        [
          {
            "node": "Mark as sent",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Report failure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark as sent": {
      "main": [
        [
          {
            "node": "Mark thread replied",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark thread replied": {
      "main": [
        [
          {
            "node": "Was it translated?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Was it translated?": {
      "main": [
        [
          {
            "node": "Show what was sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive LINE webhook": {
      "main": [
        [
          {
            "node": "Compute signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compute signature": {
      "main": [
        [
          {
            "node": "Signature valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Signature valid?": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Drop invalid request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "Split events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split events": {
      "main": [
        [
          {
            "node": "Skip redelivered events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Skip redelivered events": {
      "main": [
        [
          {
            "node": "Extract message fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract message fields": {
      "main": [
        [
          {
            "node": "Text messages only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Text messages only": {
      "main": [
        [
          {
            "node": "Process one event at a time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process one event at a time": {
      "main": [
        [],
        [
          {
            "node": "Current event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Current event": {
      "main": [
        [
          {
            "node": "Find thread",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find thread": {
      "main": [
        [
          {
            "node": "AI: Detect language and translate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI: Detect language and translate": {
      "main": [
        [
          {
            "node": "Check detection",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check detection": {
      "main": [
        [
          {
            "node": "Send acknowledgment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send acknowledgment": {
      "main": [
        [
          {
            "node": "Build Slack note",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Slack note": {
      "main": [
        [
          {
            "node": "Thread exists?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Thread exists?": {
      "main": [
        [
          {
            "node": "Reply in thread",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get LINE display name",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply in thread": {
      "main": [
        [
          {
            "node": "Update thread status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get LINE display name": {
      "main": [
        [
          {
            "node": "Create new thread",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create new thread": {
      "main": [
        [
          {
            "node": "Save thread",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update thread status": {
      "main": [
        [
          {
            "node": "Process one event at a time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save thread": {
      "main": [
        [
          {
            "node": "Process one event at a time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {}
}
