
LINE에서 사진을 보내면 AI가 견적을 보내주는 봇을 만들었다 — LINE Webhook 「검증」 버튼의 함정과 진짜 통신 확인 방법
요약
LINE Messaging API를 활용해 사진을 분석하고 견적을 제공하는 AI 봇 개발 과정에서의 트러블슈팅 사례를 다룹니다. 특히 Webhook 검증 버튼의 함정과 ngrok을 이용한 통신 확인 방법을 상세히 설명합니다.
핵심 포인트
- LINE Webhook '검증' 성공이 실제 메시지 수신을 보장하지 않음
- Webhook 동작을 위해 'Webhook 이용' 토글이 ON인지 반드시 확인 필요
- ngrok Request Inspector를 활용한 LINE-앱 간 통신 문제 구분법
- Claude API(Sonnet)를 이용한 이미지 기반 정형 데이터 추출 구현
LINE Messaging API로 봇을 만들기 시작하면, 다음과 같은 상황에 맞닥뜨리게 됩니다.
- LINE Developers 콘솔에서 Webhook의 「검증 (Verification)」 버튼을 누르면 성공이라고 뜨는데, 실제 기기에서 메시지를 보내면 봇이 무반응 - Webhook URL은 맞을 것이고, 코드도 동작할 것입니다. 그런데 어디가 잘못되었는지 구분할 수단이 없습니다 - 이미지를 받아서 AI에게 전달하고 싶은데, Webhook으로 전달되는 이벤트에는 이미지 본체가 들어있지 않습니다
이 기사는 「LINE에서 사진을 보내면, AI가 내용을 읽어 들여 정형화된 견적 텍스트를 반환하는 봇」을 개인 개발했을 때의 기록입니다. 그중에서도 가장 시간을 허비했던 Webhook 「검증」 버튼의 함정과, ngrok을 사용한 기계적인 통신 구분 방법을 중심으로 정리합니다.
이 기사에서 알 수 있는 것
- LINE Messaging API + Claude API (이미지 입력)로 사진 견적 봇을 만드는 전체 구성
- 「검증은 성공하는데 실전에서 동작하지 않을」 때 가장 먼저 확인해야 할 설정
- ngrok의 리퀘스트 인스펙터 (Request Inspector)로 「LINE 측의 문제인가, 앱 측의 문제인가」를 기계적으로 구분하는 방법
- 서명 검증 (Signature Verification)・이미지 취득・응답 시간 제한 등 구현의 요점
참고로, LINE 측의 사양에 관한 기술은 집필 시점 (2026년 7월) 기준입니다. 사양은 변경될 가능성이 있으므로, 반드시 LINE Developers의 공식 문서에서 최신 정보를 확인해 주세요.
구성 방식은 심플합니다. 사용자가 사진을 보내면, Webhook으로 받은 이미지를 Claude API에 전달하고, 읽어 들인 내용을 정형화된 견적 텍스트로 정제하여 LINE으로 반환합니다.
[사용자]
│ 사진을 전송
▼
...
모델은 비용 중시를 위해 Sonnet 계열을 채택했습니다. 「사진에서 내용을 읽어 들여, 정해진 포맷의 문장으로 정제한다」는 용도라면, 이미지 이해와 문장 정제 품질은 이것으로 충분했습니다.
먼저 결론부터 말씀드립니다.
LINE Developers 콘솔의 Webhook 「검증」 버튼은, 「Webhook 이용」 토글이 OFF 상태여도 성공합니다.
즉 「검증 OK」 = 「실전에서 메시지가 도착함」이 아닙니다.
저의 경우, 「검증」 성공 → 실제 기기에서 무반응 → 코드 측을 끝없이 디버깅하며 시간을 허비한 끝에, 콘솔의 「Webhook 이용」 토글이 OFF 상태였다는 것을 깨달았습니다.
「검증」 버튼은 설정한 URL로의 통신을 확인하는 것이지, 실제 메시지 이벤트가 배송되는 상태인지까지는 보증하지 않습니다 (집필 시점의 동작입니다). 「검증은 성공하는데 실제 메시지에는 반응하지 않는」 경우에는 코드를 의심하기 전에, 먼저 「Webhook 이용」 토글이 ON인지를 확인하십시오.
토글을 바로잡아도 동작하지 않을 때, 다음에 필요한 것은 「애초에 LINE으로부터 Webhook이 도착하고 있는가」에 대한 사실 확인입니다. 로컬 개발에서 ngrok을 사용하고 있다면, 리퀘스트 단위로 확인할 수 있습니다. ngrok 실행 중에 브라우저에서 다음 URL을 열기만 하면 됩니다.
이것은 ngrok의 리퀘스트 인스펙터로, 터널을 통과한 HTTP 리퀘스트가 목록으로 표시됩니다. 실제 기기에서 메시지를 보낸 직후 이곳을 확인하면, 구분 작업은 기계적으로 끝납니다.
| 인스펙터 표시 | 문제의 소재 | 확인할 사항 |
|---|---|---|
| 리퀘스트가 오지 않음 | LINE 측 설정 | 「Webhook 이용」 토글, Webhook URL 오류 (https인지, 경로까지 맞는지) |
| 리퀘스트는 오지만 응답이 에러 | 앱 측 | 서명 검증 실패, 라우팅, 예외로 인한 크래시 여부 |
| 리퀘스트가 오고 200을 반환하는데 답장이 없음 | 앱 측 로직 | 답장 처리 (reply/push) 구현, API 에러 로그 |
「도착하지 않았다면 LINE 측, 도착했다면 앱 측」. 이 한마디만으로도 막연한 디버깅에서 벗어날 수 있습니다.
Webhook 리퀘스트가 정말로 LINE에서 온 것인지를 x-line-signature 헤더로 검증합니다. 채널 시크릿 (Channel Secret)을 키로 한 HMAC-SHA256으로 **원래의 리퀘스트 바디 (Raw Request Body)**를 서명하고, 헤더 값과 비교합니다.
여기서 주의해야 할 점은, express.json() 등으로 파싱된 바디로부터 재구축한 JSON이 아니라, **수신한 그대로의 원본 바디 (Raw Body)**로 계산해야 한다는 것입니다. JSON을 다시 직렬화(Re-serialization)하여 바이트 열이 바뀌면 서명이 일치하지 않게 됩니다.
import express from "express";
import crypto from "crypto";
const app = express();
...
채널 시크릿(Channel Secret)이나 액세스 토큰(Access Token)은 환경 변수로 관리하고, 코드나 리포지토리(Repository)에 직접 작성하지 마세요.
Webhook으로 전달되는 이미지 메시지 이벤트에는 이미지의 메타 정보(messageId 등)만 들어 있습니다. 이미지 본체는 messageId를 사용하여 LINE의 콘텐츠 취득 엔드포인트(Endpoint)로부터 별도로 다운로드해야 합니다.
async function fetchImageAsBase64(messageId) {
const res = await fetch(
`https://api-data.line.me/v2/bot/message/${messageId}/content`,
...
취득한 이미지를 base64로 변환하여, Claude API에 이미지 블록으로서 전달합니다.
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic(); // ANTHROPIC_API_KEY 를 환경 변수에서 읽음
async function estimateFromImage(base64, mediaType) {
...
Webhook은 수신 후 신속하게 200을 반환해야 합니다 (집필 시점 확인 정보로는 기준 5초. 최신 값은 공식 문서를 확인해 주세요). 또한, 답장에 사용하는 reply token에는 유효 기간이 있습니다.
이미지 취득 → AI 호출 → 정형화는 수 초가 걸릴 수 있기 때문에, 단순히 "처리한 후 reply"라고 작성하면 타임아웃(Timeout)이나 reply token 만료를 겪게 됩니다. 전형적인 대책은 다음 패턴입니다.
- Webhook 수신 시에는
먼저 200을 반환 (위 코드 예시의 형태) - 무거운 처리는 백그라운드(Background)에서 실행 - 처리 완료 후, reply token이 아니라
push message로 사용자에게 전송
reply token의 기한 내에 확실히 끝나는 가벼운 처리라면 reply로 문제가 없지만, AI 호출을 포함하는 봇에서는 push를 전제로 설계하는 것이 더 안전했습니다.
AI의 출력을 그대로 사용자에게 반환하면, 매번 문체나 구성이 흔들려 "서비스의 응답"으로 사용하기 어려워집니다. 프롬프트(Prompt)로 출력 포맷을 고정하면 단번에 실용적으로 변했습니다.
당신은 견적 작성 어시스턴트입니다.
보내온 사진에서 확인할 수 있는 내용을 바탕으로, 반드시 다음 포맷으로 출력해 주세요.
【견적(개략적/참고치)】
...
한 가지 더, 솔직하게 적어두고 싶은 설계 판단이 있습니다. 금액의 정확성은 AI로 보장할 수 없습니다. 사진 한 장에서 읽어낼 수 있는 정보에는 한계가 있으며, 모델이 그럴듯한 숫자를 내놓을 리스크도 있습니다. 따라서 처음부터 "개략적/참고치를 반환하는 것"이라고 단정 짓고, 응답 텍스트 자체에 주의 사항을 반드시 포함하는 설계로 했습니다. "AI가 정식 견적을 내는 것"이 아니라 "1차 답변을 자동화하고, 정식 견적은 사람이 내는 것"이라는 위치 설정입니다.
- 구성은 "LINE Webhook → 이미지 취득 → Claude API (Sonnet 계열) → 정형 텍스트로 답장". 이미지 이해 + 문장 정형화라면 Sonnet 계열로 충분했다
- "검증" 버튼의 성공은 "Webhook 이용" 토글 ON을 의미하지 않는다. 검증은 OK인데 무반응이라면, 먼저 토글을 확인한다 - ngrok의 리퀘스트 인스펙터(Request Inspector,
http://127.0.0.1:4040)를 보면, "LINE에서 도착하지 않음 = LINE 측 설정", "도착함 = 앱 측"이라고 기계적으로 구분할 수 있다 - 서명 검증은 Raw Body로 HMAC-SHA256, 이미지는 content 엔드포인트에서 별도로 취득, 무거운 처리는 먼저 200을 반환하고 push로 답장 - 출력 포맷은 프롬프트로 고정하고, 금액은 "개략적/참고치"라고 명기하는 설계로 한다
LINE 측의 사양(검증 버튼의 동작, 응답 시간, 엔드포인트)은 집필 시점의 정보입니다. 구현 전에 공식 문서에서 최신 사양을 확인해 주세요. 같은 곳에서 헤매고 계신 분들의 시간을 절약할 수 있다면 좋겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기