{
  "name": "인스타 댓글 키워드 → 팔로우 확인 후 자동 DM (Private Reply)",
  "nodes": [
    {
      "parameters": {
        "path": "ig-comment-dm",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Meta 웹훅 검증 (GET)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        1408,
        -96
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "v1",
              "leftValue": "={{ $json.query['hub.verify_token'] }}",
              "rightValue": "여기에_내가_정한_verify_token",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "name": "verify_token 일치?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1632,
        -96
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "={{ $('Meta 웹훅 검증 (GET)').item.json.query['hub.challenge'] }}",
        "options": {}
      },
      "name": "challenge 돌려주기",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [
        1840,
        -176
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "forbidden",
        "options": {
          "responseCode": 403
        }
      },
      "name": "403 거절",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [
        1840,
        -16
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "ig-comment-dm",
        "options": {
          "rawBody": true
        }
      },
      "name": "댓글 이벤트 수신 (POST)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        1408,
        224
      ]
    },
    {
      "parameters": {
        "jsCode": "// Meta 서명 검증은 '받은 그대로의 원문'으로 해야 합니다.\nconst items = $input.all();\nconst out = [];\nfor (let i = 0; i < items.length; i++) {\n  const buf = await this.helpers.getBinaryDataBuffer(i, 'data');\n  const sig = String(items[i].json.headers['x-hub-signature-256'] || '').replace('sha256=', '');\n  out.push({ json: { raw: buf.toString('utf8'), signature: sig, body: items[i].json.body } });\n}\nreturn out;"
      },
      "name": "원문·서명 꺼내기",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1632,
        224
      ]
    },
    {
      "parameters": {
        "action": "hmac",
        "value": "={{ $json.raw }}",
        "dataPropertyName": "expected"
      },
      "name": "서명 계산 (HMAC-SHA256)",
      "type": "n8n-nodes-base.crypto",
      "typeVersion": 2,
      "position": [
        1840,
        224
      ],
      "credentials": {
        "crypto": {
          "id": "",
          "name": "인스타 앱 시크릿 (Crypto)"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "v2",
              "leftValue": "={{ $json.expected }}",
              "rightValue": "={{ $json.signature }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "name": "Meta가 보낸 게 맞나?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2064,
        224
      ]
    },
    {
      "parameters": {
        "jsCode": "// 웹훅 payload에서 댓글만 꺼냅니다. 내 계정이 단 댓글(자동 답글 포함)은 제외합니다.\nconst out = [];\nfor (const item of $input.all()) {\n  const body = item.json.body || {};\n  for (const entry of body.entry || []) {\n    for (const ch of entry.changes || []) {\n      if (ch.field !== 'comments') continue;\n      const v = ch.value || {};\n      if (!v.id || !v.from) continue;\n      if (String(v.from.id) === String(entry.id)) continue;\n      out.push({ json: {\n        ig_id: String(entry.id),\n        comment_id: String(v.id),\n        media_id: String(v.media?.id || ''),\n        username: v.from.username || '',\n        text: v.text || '',\n      } });\n    }\n  }\n}\nreturn out;"
      },
      "name": "댓글만 꺼내기",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2288,
        176
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "dm_rules"
        },
        "returnAll": true
      },
      "name": "규칙 불러오기",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        2512,
        176
      ],
      "executeOnce": true,
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "jsCode": "// 규칙 테이블(dm_rules)에 등록된 게시물만 처리합니다.\n// 게시물별로 키워드를 확인하고, 보낼 자료 이름·링크를 붙입니다.\nconst rules = $('규칙 불러오기').all().map(i => i.json).filter(r => r.media_id);\nconst norm = s => String(s || '').toUpperCase().replace(/\\s+/g, '');\nconst out = [];\nfor (const c of $('댓글만 꺼내기').all().map(i => i.json)) {\n  const rule = rules.find(r => String(r.media_id) === c.media_id);\n  if (!rule) continue;                                   // 등록 안 된 게시물은 무시\n  if (!norm(c.text).includes(norm(rule.keyword))) continue; // 키워드 없는 댓글은 무시\n  out.push({ json: { ...c, keyword: rule.keyword, title: rule.title, link: rule.link } });\n}\nreturn out;"
      },
      "name": "키워드 확인",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2720,
        176
      ]
    },
    {
      "parameters": {
        "operation": "rowNotExists",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "dm_log"
        },
        "filters": {
          "conditions": [
            {
              "keyName": "comment_id",
              "keyValue": "={{ $json.comment_id }}"
            }
          ]
        }
      },
      "name": "이미 보낸 댓글 제외",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        2944,
        176
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://graph.instagram.com/v24.0/{{ $json.ig_id }}/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ recipient: { comment_id: $json.comment_id }, message: { text: '요청 고마워요! 자료는 @{계정핸들} 팔로워분들께 보내드리고 있어요.\\n\\n1. @{계정핸들} 팔로우하기\\n2. 아래 [팔로우 완료] 버튼 누르기\\n(버튼이 안 보이면 「완료」라고 답장 주세요)\\n\\n팔로우가 확인되면 바로 링크 보내드릴게요.', quick_replies: [{ content_type: 'text', title: '팔로우 완료', payload: 'FOLLOW_CHECK' }] } }) }}",
        "options": {}
      },
      "name": "팔로우 안내 DM (Private Reply)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3168,
        176
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "",
          "name": "인스타 토큰 (Header Auth)"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://graph.instagram.com/v24.0/{{ $('이미 보낸 댓글 제외').item.json.ig_id }}/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ recipient: { comment_id: $('이미 보낸 댓글 제외').item.json.comment_id }, message: { text: '요청 고마워요! 자료는 @{계정핸들} 팔로워분들께 보내드리고 있어요.\\n\\n1. @{계정핸들} 팔로우하기\\n2. 아래 [팔로우 완료] 버튼 누르기\\n(버튼이 안 보이면 「완료」라고 답장 주세요)\\n\\n팔로우가 확인되면 바로 링크 보내드릴게요.' } }) }}",
        "options": {}
      },
      "name": "팔로우 안내 DM (버튼 없이 재시도)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3392,
        336
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "",
          "name": "인스타 토큰 (Header Auth)"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// DM 응답의 recipient_id(IGSID)를 댓글 정보에 붙입니다. 이 ID로 나중에 DM 답장을 찾아옵니다.\nreturn { json: { ...$('이미 보낸 댓글 제외').item.json, igsid: String($json.recipient_id || '') } };"
      },
      "name": "IGSID 붙이기",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3600,
        176
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://graph.instagram.com/v24.0/{{ $json.comment_id }}/replies",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "message",
              "value": "={{ ['DM으로 안내 보냈어요. 메시지(요청함)를 확인해 주세요.', '안내를 DM으로 보냈어요. 안 보이면 메시지 요청함도 확인해 주세요.', 'DM 보냈어요! 확인해 주세요.'][Math.floor(Math.random()*3)] }}"
            }
          ]
        },
        "options": {}
      },
      "name": "댓글에 답글 달기",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3824,
        176
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "",
          "name": "인스타 토큰 (Header Auth)"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "dm_log"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "comment_id": "={{ $('IGSID 붙이기').item.json.comment_id }}",
            "media_id": "={{ $('IGSID 붙이기').item.json.media_id }}",
            "username": "={{ $('IGSID 붙이기').item.json.username }}",
            "comment_text": "={{ $('IGSID 붙이기').item.json.text }}",
            "status": "awaiting_follow",
            "error": "",
            "igsid": "={{ $('IGSID 붙이기').item.json.igsid }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "comment_id",
              "displayName": "comment_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "media_id",
              "displayName": "media_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "username",
              "displayName": "username",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "comment_text",
              "displayName": "comment_text",
              "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": "error",
              "displayName": "error",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "igsid",
              "displayName": "igsid",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "name": "기록 (팔로우 대기)",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        4048,
        176
      ]
    },
    {
      "parameters": {
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "dm_log"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "comment_id": "={{ $('이미 보낸 댓글 제외').item.json.comment_id }}",
            "media_id": "={{ $('이미 보낸 댓글 제외').item.json.media_id }}",
            "username": "={{ $('이미 보낸 댓글 제외').item.json.username }}",
            "comment_text": "={{ $('이미 보낸 댓글 제외').item.json.text }}",
            "status": "failed",
            "error": "={{ $json.error?.message || JSON.stringify($json).slice(0, 500) }}",
            "igsid": ""
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "comment_id",
              "displayName": "comment_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "media_id",
              "displayName": "media_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "username",
              "displayName": "username",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "comment_text",
              "displayName": "comment_text",
              "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": "error",
              "displayName": "error",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "igsid",
              "displayName": "igsid",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "name": "실패 기록 (failed)",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        3600,
        432
      ]
    },
    {
      "parameters": {
        "jsCode": "// DM 웹훅에서 사용자가 보낸 메시지만 꺼냅니다. 내가 보낸 메시지(echo)는 제외합니다.\nconst out = [];\nfor (const item of $input.all()) {\n  for (const entry of item.json.body?.entry || []) {\n    for (const m of entry.messaging || []) {\n      if (!m.message || m.message.is_echo) continue;\n      if (String(m.sender?.id) === String(entry.id)) continue;\n      out.push({ json: {\n        ig_id: String(entry.id),\n        igsid: String(m.sender.id),\n        text: m.message.text || '',\n        payload: m.message.quick_reply?.payload || '',\n      } });\n    }\n  }\n}\nreturn out;"
      },
      "name": "DM 답장만 꺼내기",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2288,
        544
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "dm_log"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "igsid",
              "keyValue": "={{ $json.igsid }}"
            },
            {
              "keyName": "status",
              "keyValue": "awaiting_follow"
            }
          ]
        },
        "returnAll": true
      },
      "name": "팔로우 대기 요청 찾기",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        2512,
        544
      ]
    },
    {
      "parameters": {
        "jsCode": "// 같은 사람의 대기 요청을 하나로 묶습니다. 대기 요청이 없는 DM(일반 문의)은 여기서 멈춥니다.\nconst dm = $('DM 답장만 꺼내기').first().json;\nconst rows = $input.all().map(i => i.json).filter(r => r.igsid === dm.igsid);\nif (!rows.length) return [];\nreturn [{ json: { ig_id: dm.ig_id, igsid: dm.igsid, media_ids: [...new Set(rows.map(r => String(r.media_id)))] } }];"
      },
      "name": "요청 묶기",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2720,
        544
      ]
    },
    {
      "parameters": {
        "url": "=https://graph.instagram.com/v24.0/{{ $json.igsid }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "fields",
              "value": "={{ ['DM으로 안내 보냈어요. 메시지(요청함)를 확인해 주세요.', '안내를 DM으로 보냈어요. 안 보이면 메시지 요청함도 확인해 주세요.', 'DM 보냈어요! 확인해 주세요.'][Math.floor(Math.random()*3)] }}"
            }
          ]
        },
        "options": {}
      },
      "name": "팔로우 여부 확인",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2944,
        544
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "",
          "name": "인스타 토큰 (Header Auth)"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "f1",
              "leftValue": "={{ $json.is_user_follow_business }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "name": "팔로우 중?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3168,
        544
      ]
    },
    {
      "parameters": {
        "jsCode": "// 대기 중인 게시물들의 링크를 규칙 표에서 찾아 한 메시지로 만듭니다.\nconst req = $('요청 묶기').first().json;\nconst rules = $('규칙 불러오기 (DM)').all().map(i => i.json).filter(r => r.media_id);\nconst lines = req.media_ids.map(id => rules.find(r => String(r.media_id) === id)).filter(Boolean).map(r => r.title + '\\n' + r.link);\nconst text = '팔로우 확인됐어요. 고마워요!\\n\\n' + (lines.length ? lines.join('\\n\\n') : 'https://내-사이트.example/목록') + '\\n\\n링크에서 자료를 받아 보세요. 감사합니다!';\nreturn [{ json: { ig_id: req.ig_id, igsid: req.igsid, text } }];"
      },
      "name": "링크 메시지 만들기",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3600,
        464
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "dm_rules"
        },
        "returnAll": true
      },
      "name": "규칙 불러오기 (DM)",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        3392,
        464
      ],
      "executeOnce": true,
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://graph.instagram.com/v24.0/{{ $json.ig_id }}/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ recipient: { id: $json.igsid }, message: { text: $json.text } }) }}",
        "options": {}
      },
      "name": "링크 DM 보내기",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3824,
        464
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "",
          "name": "인스타 토큰 (Header Auth)"
        }
      }
    },
    {
      "parameters": {
        "operation": "update",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "dm_log"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "igsid",
              "keyValue": "={{ $('요청 묶기').first().json.igsid }}"
            },
            {
              "keyName": "status",
              "keyValue": "awaiting_follow"
            }
          ]
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "status": "sent"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "comment_id",
              "displayName": "comment_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "media_id",
              "displayName": "media_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "username",
              "displayName": "username",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "comment_text",
              "displayName": "comment_text",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "error",
              "displayName": "error",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "igsid",
              "displayName": "igsid",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "name": "기록 갱신 (sent)",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        4048,
        464
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://graph.instagram.com/v24.0/{{ $('요청 묶기').first().json.ig_id }}/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ recipient: { id: $('요청 묶기').first().json.igsid }, message: { text: '아직 팔로우가 확인되지 않아요. @{계정핸들} 팔로우 후 [팔로우 완료]를 다시 눌러주세요. (반영까지 몇 초 걸릴 수 있어요)', quick_replies: [{ content_type: 'text', title: '팔로우 완료', payload: 'FOLLOW_CHECK' }] } }) }}",
        "options": {}
      },
      "name": "팔로우 재안내",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3392,
        672
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "",
          "name": "인스타 토큰 (Header Auth)"
        }
      }
    },
    {
      "parameters": {
        "operation": "update",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "dm_log"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "igsid",
              "keyValue": "={{ $('요청 묶기').first().json.igsid }}"
            },
            {
              "keyName": "status",
              "keyValue": "awaiting_follow"
            }
          ]
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "error": "={{ '팔로우 확인 실패: ' + ($json.error?.message || JSON.stringify($json).slice(0, 300)) }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "comment_id",
              "displayName": "comment_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "media_id",
              "displayName": "media_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "username",
              "displayName": "username",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "comment_text",
              "displayName": "comment_text",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "error",
              "displayName": "error",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "igsid",
              "displayName": "igsid",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "name": "확인 실패 기록",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        3168,
        768
      ],
      "executeOnce": true
    }
  ],
  "connections": {
    "Meta 웹훅 검증 (GET)": {
      "main": [
        [
          {
            "node": "verify_token 일치?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "verify_token 일치?": {
      "main": [
        [
          {
            "node": "challenge 돌려주기",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "403 거절",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "댓글 이벤트 수신 (POST)": {
      "main": [
        [
          {
            "node": "원문·서명 꺼내기",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "원문·서명 꺼내기": {
      "main": [
        [
          {
            "node": "서명 계산 (HMAC-SHA256)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "서명 계산 (HMAC-SHA256)": {
      "main": [
        [
          {
            "node": "Meta가 보낸 게 맞나?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Meta가 보낸 게 맞나?": {
      "main": [
        [
          {
            "node": "댓글만 꺼내기",
            "type": "main",
            "index": 0
          },
          {
            "node": "DM 답장만 꺼내기",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "댓글만 꺼내기": {
      "main": [
        [
          {
            "node": "규칙 불러오기",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "규칙 불러오기": {
      "main": [
        [
          {
            "node": "키워드 확인",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "키워드 확인": {
      "main": [
        [
          {
            "node": "이미 보낸 댓글 제외",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "이미 보낸 댓글 제외": {
      "main": [
        [
          {
            "node": "팔로우 안내 DM (Private Reply)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "팔로우 안내 DM (Private Reply)": {
      "main": [
        [
          {
            "node": "IGSID 붙이기",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "팔로우 안내 DM (버튼 없이 재시도)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "팔로우 안내 DM (버튼 없이 재시도)": {
      "main": [
        [
          {
            "node": "IGSID 붙이기",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "실패 기록 (failed)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IGSID 붙이기": {
      "main": [
        [
          {
            "node": "댓글에 답글 달기",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "댓글에 답글 달기": {
      "main": [
        [
          {
            "node": "기록 (팔로우 대기)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "DM 답장만 꺼내기": {
      "main": [
        [
          {
            "node": "팔로우 대기 요청 찾기",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "팔로우 대기 요청 찾기": {
      "main": [
        [
          {
            "node": "요청 묶기",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "요청 묶기": {
      "main": [
        [
          {
            "node": "팔로우 여부 확인",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "팔로우 여부 확인": {
      "main": [
        [
          {
            "node": "팔로우 중?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "확인 실패 기록",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "팔로우 중?": {
      "main": [
        [
          {
            "node": "규칙 불러오기 (DM)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "팔로우 재안내",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "규칙 불러오기 (DM)": {
      "main": [
        [
          {
            "node": "링크 메시지 만들기",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "링크 메시지 만들기": {
      "main": [
        [
          {
            "node": "링크 DM 보내기",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "링크 DM 보내기": {
      "main": [
        [
          {
            "node": "기록 갱신 (sent)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "nodeGroups": []
}