텔레그램 웹훅 설정 가이드: setWebhook, secret_token, getWebhookInfo
텔레그램 웹훅(webhook)은 봇에 새 업데이트가 생길 때마다 텔레그램 서버가 여러분의 HTTPS 주소로 JSON을 POST해 주는 방식입니다. 텔레그램 봇 웹훅 설정은 setWebhook에 공개 HTTPS URL을 등록하면 끝나고, getWebhookInfo로 상태를 확인하며, deleteWebhook으로 언제든 getUpdates 방식으로 돌아갈 수 있습니다. 이 글에서는 설정 절차, secret_token을 이용한 요청 검증 코드, 자주 만나는 오류의 해결 방법을 설명합니다.
웹훅과 getUpdates(롱 폴링)의 차이
봇이 업데이트를 받는 방법은 두 가지이며, 서로 배타적이라 동시에 쓸 수 없습니다. 어느 방식이든 받아 가지 않은 업데이트는 서버에 최대 24시간까지만 보관됩니다.
| 항목 | getUpdates (롱 폴링) | 웹훅 (setWebhook) |
|---|---|---|
| 동작 방식 | 봇이 텔레그램에 업데이트를 요청(pull) | 텔레그램이 봇 서버로 전송(push) |
| 필요한 것 | 인터넷 연결만 있으면 됨 | 공개 도메인, HTTPS 인증서, 허용된 포트 |
| 잘 맞는 경우 | 로컬 개발, 방화벽 내부 서버, 소규모 봇 | 운영 서버, 서버리스 함수, 트래픽이 많은 봇 |
| 주의할 점 | 토큰당 폴링 프로그램은 하나만 | 응답이 느리거나 실패하면 재전송이 발생 |
웹훅을 쓰면 봇이 주기적으로 요청을 보낼 필요가 없고, 업데이트가 생기는 즉시 전달받습니다. 반대로 지금 getUpdates로 잘 동작하고 있다면 굳이 바꿀 필요는 없습니다. 개발 단계에서는 롱 폴링, 운영 서버에서는 웹훅으로 나누어 쓰는 구성도 흔합니다. 아직 봇이 없다면 텔레그램 봇 만들기부터 진행하십시오.
웹훅 서버 요구 사항
- HTTPS 필수: 일반 HTTP 주소는 등록할 수 없습니다. TLS 1.2 이상만 지원하며 SSLv2/3, TLS 1.0/1.1은 거부됩니다.
- 허용 포트: 443, 80, 88, 8443만 쓸 수 있습니다. 443이 아니면 URL에 포트를 명시합니다(예:
https://bot.example.com:8443/telegram/webhook). - 인증서: 신뢰할 수 있는 인증 기관의 인증서라면 별도 작업이 필요 없습니다. 인증서의 CN 또는 SAN이 도메인과 일치해야 하고, 중간 인증서까지 포함한 전체 체인을 제공해야 합니다. 자체 서명 인증서는
certificate파라미터로 공개 키 인증서(PEM)를 업로드합니다. - IPv4: 웹훅은 현재 IPv6를 지원하지 않습니다.
- 리디렉션 불가: 웹훅 주소가 다른 주소로 리디렉션하면 동작하지 않습니다.
방화벽으로 접근을 제한하려면 텔레그램이 안내하는 149.154.160.0/20과 91.108.4.0/22 대역을 허용하십시오. 이 대역은 바뀔 수 있으므로 문제가 생기면 공식 웹훅 가이드를 다시 확인해야 합니다.
참고로 오픈 소스로 공개된 Bot API 서버를 직접 운영하는 경우(로컬 Bot API 서버)에는 HTTP 주소, 임의의 포트, 로컬 IP 주소도 웹훅에 쓸 수 있습니다. 다만 대부분의 봇은 기본 서버인 api.telegram.org와 위의 요구 사항만으로 충분합니다.
setWebhook으로 텔레그램 봇 웹훅 설정하기
| 파라미터 | 설명 |
|---|---|
url | 필수. 업데이트를 받을 HTTPS 주소. 빈 문자열을 보내면 웹훅이 해제됩니다. |
secret_token | 1~256자, A-Z a-z 0-9 _ -만 허용. 모든 요청의 X-Telegram-Bot-Api-Secret-Token 헤더로 전달됩니다. |
allowed_updates | 받을 업데이트 종류 목록(예: ["message", "callback_query"]). 지정하지 않으면 이전 설정이 유지됩니다. |
drop_pending_updates | true면 쌓여 있던 업데이트를 버리고 시작합니다. |
max_connections | 동시 HTTPS 연결 수 1~100, 기본값 40. |
certificate | 자체 서명 인증서의 공개 키 파일. multipart/form-data로 업로드합니다. |
ip_address | DNS 조회 대신 사용할 고정 IP 주소. |
먼저 추측하기 어려운 비밀 값을 만듭니다. openssl rand -hex 32는 허용 문자만으로 된 64자 문자열을 만들어 줍니다.
SECRET=$(openssl rand -hex 32)
curl -X POST "https://api.telegram.org/bot123456789:AAEexampleTokenReplaceWithYours/setWebhook" \
-H "Content-Type: application/json" \
-d '{
"url": "https://bot.example.com/telegram/webhook",
"secret_token": "'"$SECRET"'",
"allowed_updates": ["message", "callback_query"],
"drop_pending_updates": true,
"max_connections": 40
}'
성공하면 {"ok":true,"result":true,"description":"Webhook was set"}가 반환됩니다. SECRET 값은 서버의 환경 변수에도 똑같이 넣어 두어야 합니다. 자체 서명 인증서를 쓴다면 파일 업로드가 필요하므로 JSON 대신 폼 형식으로 보냅니다.
curl -F "url=https://203.0.113.10:8443/telegram/webhook" \
-F "certificate=@public.pem" \
-F "secret_token=$SECRET" \
"https://api.telegram.org/bot123456789:AAEexampleTokenReplaceWithYours/setWebhook"
터미널 대신 Bot API Tester의 setWebhook에서 파라미터를 채워 호출해도 됩니다.
getWebhookInfo로 웹훅 상태 확인
설정이 끝나면 반드시 getWebhookInfo로 실제 상태를 확인하십시오. 파라미터 없이 호출하며, 웹훅이 없으면 url이 빈 문자열로 나옵니다.
curl "https://api.telegram.org/bot123456789:AAEexampleTokenReplaceWithYours/getWebhookInfo"
{
"ok": true,
"result": {
"url": "https://bot.example.com/telegram/webhook",
"has_custom_certificate": false,
"pending_update_count": 3,
"last_error_date": 1793266500,
"last_error_message": "Wrong response from the webhook: 404 Not Found",
"max_connections": 40,
"allowed_updates": ["message", "callback_query"]
}
}
pending_update_count: 아직 전달되지 못한 업데이트 수입니다. 계속 늘어난다면 서버가 2xx로 응답하지 못하고 있다는 뜻입니다.last_error_message와last_error_date: 가장 최근 전달 실패의 내용과 시각(Unix time)입니다. 위 예시는 URL 경로가 서버 라우트와 맞지 않는 경우입니다.has_custom_certificate:certificate로 인증서를 업로드했는지 여부입니다.
같은 내용을 Bot API Tester의 getWebhookInfo에서 버튼 한 번으로 확인할 수 있습니다.
웹훅 핸들러 작성: secret_token 헤더 검증
웹훅 주소는 공개되어 있으므로 누구나 가짜 업데이트를 POST할 수 있습니다. 핸들러는 처리 전에 X-Telegram-Bot-Api-Secret-Token 헤더가 설정한 값과 같은지 확인해야 합니다. 다음은 Python(Flask) 예제입니다.
import hmac
import os
import requests
from flask import Flask, abort, request
TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
SECRET = os.environ["TELEGRAM_WEBHOOK_SECRET"]
API = f"https://api.telegram.org/bot{TOKEN}"
app = Flask(__name__)
@app.post("/telegram/webhook")
def telegram_webhook():
received = request.headers.get("X-Telegram-Bot-Api-Secret-Token", "")
if not hmac.compare_digest(received, SECRET):
abort(403)
update = request.get_json(silent=True) or {}
message = update.get("message")
if message and "text" in message:
requests.post(f"{API}/sendMessage", json={
"chat_id": message["chat"]["id"],
"text": "받은 메시지: " + message["text"],
}, timeout=10)
return "", 200
Node.js(Express)에서는 웹훅 응답 본문에 호출할 메서드를 담아 바로 답장할 수도 있습니다. 별도의 API 요청이 줄어드는 대신, 이 방식으로 보낸 요청은 성공 여부나 결과를 확인할 수 없습니다.
import express from "express";
const app = express();
app.use(express.json());
app.post("/telegram/webhook", (req, res) => {
if (req.get("X-Telegram-Bot-Api-Secret-Token") !== process.env.TELEGRAM_WEBHOOK_SECRET) {
return res.sendStatus(403);
}
const msg = req.body.message;
if (msg?.text) {
return res.json({ method: "sendMessage", chat_id: msg.chat.id, text: `받은 메시지: ${msg.text}` });
}
res.sendStatus(200);
});
// HTTPS(443)는 앞단의 리버스 프록시나 호스팅 플랫폼이 처리합니다
app.listen(3000);
핸들러를 작성할 때는 다음 원칙을 지키십시오.
- 빠르게 2xx로 응답: 텔레그램은 2xx가 아닌 응답을 받으면 같은 업데이트를 다시 보내다가 일정 횟수 후 포기합니다. AI 모델 호출처럼 오래 걸리는 작업은 큐에 넣고 먼저 200을 반환합니다.
- 중복 처리 방지: 재전송 때문에 같은 업데이트가 두 번 올 수 있으므로
update_id를 기록해 이미 처리한 업데이트는 건너뜁니다. - 예외는 로그로: 처리 중 오류가 나도 가능하면 200을 반환하고 로그에 남깁니다. 500을 반환하면 같은 업데이트가 반복해서 들어와 대기열이 막힙니다.
deleteWebhook으로 getUpdates 방식으로 되돌리기
로컬에서 디버깅하거나 서버를 옮길 때는 웹훅을 해제하고 롱 폴링으로 돌아갑니다. 웹훅이 설정된 동안에는 getUpdates가 동작하지 않습니다.
curl "https://api.telegram.org/bot123456789:AAEexampleTokenReplaceWithYours/deleteWebhook?drop_pending_updates=true"
성공하면 Webhook was deleted가, 이미 웹훅이 없다면 Webhook is already deleted가 반환됩니다. drop_pending_updates를 빼면 쌓여 있던 업데이트는 그대로 남아 다음 getUpdates 호출에서 받을 수 있습니다. 브라우저에서는 deleteWebhook으로 실행할 수 있습니다.
폴링에서 웹훅으로 전환할 때 체크리스트
- 도메인과 인증서 확인: 외부 네트워크에서
curl -v https://bot.example.com/telegram/webhook을 실행해 HTTPS 접속이 되는지, 인증서 오류가 없는지 확인합니다. - 핸들러 먼저 배포:
secret_token검증과 2xx 응답을 구현한 핸들러를 먼저 배포합니다. 웹훅부터 등록하면 준비되지 않은 서버로 업데이트가 몰려 실패가 쌓입니다. - 폴링 중지: 기존
getUpdates프로세스를 종료합니다. 남아 있으면 409 오류를 반복해서 받습니다. - setWebhook 호출: 사용자 메시지를 하나도 놓치면 안 된다면
drop_pending_updates를 빼고, 테스트 중 쌓인 업데이트라면true로 버립니다. - 상태 확인:
getWebhookInfo에서url과allowed_updates가 의도대로인지,pending_update_count가 0으로 줄어드는지 봅니다. - 실제 메시지로 검증: 봇에게 메시지를 보내 답장이 오는지, 서버 로그에 요청이 기록되는지 확인합니다.
allowed_updates는 지정하지 않으면 이전 설정이 그대로 유지되고, 호출 전에 이미 생성된 업데이트에는 적용되지 않습니다. 설정을 바꾼 직후 잠시 원하지 않는 종류의 업데이트가 들어올 수 있으므로, 핸들러는 처리하지 않는 업데이트를 조용히 무시하고 200을 반환하도록 작성합니다. max_connections는 기본값 40으로 충분한 경우가 많으며, 서버 부하를 줄이려면 낮추고 처리량을 높이려면 올립니다.
텔레그램 웹훅 오류와 해결 방법
| 오류·증상 | 원인 | 해결 |
|---|---|---|
Bad Request: bad webhook: An HTTPS URL must be provided for webhook | http:// 주소를 등록함(예전 버전의 문구는 HTTPS url must be provided for webhook) | 유효한 인증서가 있는 https:// 주소를 사용합니다. |
Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 or 8443 | 허용되지 않은 포트 | 443, 80, 88, 8443 중 하나로 바꿉니다. |
Bad Request: secret token contains illegal characters | secret_token에 허용되지 않은 문자 포함 | 영문, 숫자, _, -만 사용합니다. |
409 Conflict: can't use getUpdates method while webhook is active | 웹훅이 설정된 상태에서 폴링을 시도함 | deleteWebhook을 호출하거나 폴링 코드를 멈춥니다. |
last_error_message가 Wrong response from the webhook: … | 서버가 404, 500, 502 등 2xx가 아닌 응답을 반환 | 경로, 라우트, 리버스 프록시 설정과 서버 로그를 확인합니다. |
pending_update_count가 계속 증가 | 서버에 접속할 수 없거나 인증서 체인이 불완전함 | 외부에서 HTTPS 접속을 확인하고 중간 인증서를 포함합니다. |
n8n을 localhost에서 실행하는 경우: 텔레그램 서버는 인터넷 너머에 있으므로 localhost나 http:// 주소로는 웹훅 요청을 보낼 수 없습니다. n8n처럼 웹훅으로 텔레그램 업데이트를 받는 도구를 로컬에서 쓰려면 터널링 서비스로 HTTPS 공개 주소를 만들거나, 도메인과 유효한 인증서가 있는 서버에 배포해야 합니다. 이때 같은 봇으로 다른 곳에서 getUpdates를 호출하면 409 오류가 나므로, 도구용 봇을 따로 만들어 두면 편합니다. 웹훅 설정 후 메시지를 보내는 방법은 텔레그램 봇 API 메시지 보내기에서, chat id 확인은 텔레그램 chat id 확인방법에서 다룹니다.
자주 묻는 질문
텔레그램 웹훅은 어떻게 설정하나요?
https://api.telegram.org/bot<token>/setWebhook에 공개 HTTPS 주소를 url로 넘겨 호출합니다. 성공하면 "result": true와 Webhook was set이 반환되고, 이후 새 업데이트가 그 주소로 POST됩니다.
웹훅을 설정한 뒤 getUpdates가 409 Conflict를 반환합니다.
웹훅과 getUpdates는 동시에 쓸 수 없습니다. 폴링으로 돌아가려면 deleteWebhook을 호출하고, 웹훅을 유지하려면 getUpdates 호출을 멈추십시오.
secret_token은 무엇이고 어떻게 검증하나요?
setWebhook에 1~256자의 secret_token(A-Z, a-z, 0-9, _, -)을 지정하면 모든 웹훅 요청의 X-Telegram-Bot-Api-Secret-Token 헤더에 그 값이 담깁니다. 핸들러에서 헤더 값이 다르면 요청을 거부하십시오.
localhost에서 텔레그램 웹훅을 테스트할 수 있나요?
직접은 불가능합니다. 텔레그램 서버가 접속할 수 있는 공개 HTTPS 주소가 필요하므로 터널링 서비스로 로컬 서버를 HTTPS로 노출하거나, 개발 중에는 getUpdates 롱 폴링을 쓰십시오.
텔레그램 웹훅을 삭제(해제)하려면 어떻게 하나요?
deleteWebhook을 호출합니다. 쌓여 있던 업데이트까지 버리려면 drop_pending_updates=true를 함께 넘깁니다. setWebhook에 빈 url을 보내도 웹훅이 해제됩니다.
웹훅에 사용할 수 있는 포트는 무엇인가요?
443, 80, 88, 8443 네 가지뿐입니다. 어느 포트를 쓰든 HTTPS(TLS 1.2 이상)여야 하며, 다른 포트를 지정하면 Webhook can be set up only on ports 80, 88, 443 or 8443 오류가 납니다.
관련 가이드
텔레그램 봇 만들기: @BotFather로 생성하고 첫 메시지 보내기
텔레그램 봇 만들기 가이드입니다. @BotFather의 /newbot으로 봇을 생성하고 토큰을 getMe로 확인한 뒤, getUpdates와 sendMessage로 첫 메시지를 보내는 과정을 curl과 Python 예제로 설명합니다.
텔레그램 봇 API로 메시지 보내기: sendMessage 사용법과 예제
텔레그램 봇 API 메시지 보내기 가이드입니다. sendMessage를 curl·Python·JavaScript로 호출하는 예제, HTML·MarkdownV2 서식, 인라인 버튼, 그룹·채널 전송, 400·403·429 오류 해결법을 정리했습니다.
텔레그램 봇 토큰: 발급부터 확인, 폐기, 안전한 보관까지
텔레그램 봇 토큰을 @BotFather에서 발급받는 법, 기존 봇의 토큰 찾기, getMe로 유효성 확인, 유출 시 폐기 방법을 정리했습니다. n8n·AI 비서 연동에 필요한 bot token과 chat id도 안내합니다.
텔레그램 chat id 확인방법: 개인, 그룹, 채널, 토픽별 chat id 찾기
텔레그램 chat id 확인방법을 개인 채팅, 그룹, 슈퍼그룹, 채널, 포럼 토픽별로 정리했습니다. getUpdates로 chat id를 찾는 법, -100으로 시작하는 ID의 의미, 결과가 비어 있을 때 해결법까지 설명합니다.