"Stripe 결제 완료를 우리 시스템에 어떻게 알릴까?" — 답에 따라 아키텍처가 갈린다. Webhook, polling, Server-Sent Events (SSE), WebSocket — 모두 "한쪽의 변화를 다른 쪽이 알게 하는" 패턴이지만 지연 시간·비용·복잡도가 매우 다르다. 이 가이드는 네 가지를 비교하고 어떤 상황에 어느 패턴을 골라야 하는지 정리한다.
네 가지 패턴 한 줄 요약
| 패턴 | 방향 | 지연 | 비용 | 전형 사례 |
|---|---|---|---|---|
| Polling | Client → Server (반복 pull) | interval (수 초~분) | 요청 빈도 × n | Slack 알림 전 단계, 후행 작업 status |
| Webhook | Server → Server (event push) | 거의 즉시 | 이벤트당 1 회 | Stripe 결제, GitHub push, Slack 슬래시 |
| SSE | Server → Client (스트림) | 즉시 | connection 1 개 유지 | chat·dashboard 단방향 알림 |
| WebSocket | 양방향 (full-duplex) | 즉시 | connection 1 개 양방향 | 실시간 협업, 게임, 거래소 |
1. Polling — 가장 단순, 가장 둔감
클라이언트가 일정 간격으로 서버에 "변경됐어?" 묻는다.
setInterval(async () => {
const res = await fetch("/api/job/123/status");
const {status} = await res.json();
if (status === "done") {
showResult();
return;
}
}, 5000); // 5 초 간격장점:
- 인프라 0 — 표준 HTTP 만. 방화벽·프록시 모두 통과.
- 실패 시 복구 = 다음 polling. 무상태.
- 구현 5 분.
단점:
- 지연 = polling interval. 1 초마다 polling 하면 origin 부하 60× 분당.
- 요청 대부분이 "변경 없음" → 낭비. 9 / 10 응답이 304 또는 같은 데이터.
Long polling — polling 변형
클라이언트 요청을 서버가 즉시 응답 안 하고 변경 발생까지 hold (최대 N 초). 변경 발생 시 응답, 아니면 timeout 후 클라이언트 재요청.
- 장점: 지연 즉시 (변경 시).
- 단점: connection 점유. 일부 프록시 timeout 짧음. SSE/WebSocket 이 등장 후 거의 폐기.
2. Webhook — Server-to-Server Push
제공자가 이벤트 발생 시 HTTP POST 로 우리 서버에 직접 알림. Stripe·GitHub·Slack 등 모든 SaaS API 의 표준.
// Webhook 수신 엔드포인트
POST /webhooks/stripe
Stripe-Signature: t=...,v1=...
Content-Type: application/json
{ "type": "charge.succeeded", "data": {...} }장점:
- 지연 거의 즉시.
- 요청 = 이벤트 수 (낭비 0).
- 클라이언트 connection 점유 X.
단점:
- 수신 서버가 public endpoint 필요. localhost·내부망 X. 개발 환경은 ngrok/cloudflared tunnel 필요.
- 요청 출처 확인 = 보안 핵심. 서명 검증 필수 ( HMAC 검증기 의 constant-time 비교).
- 제공자 측 retry 정책에 의존. 우리 서버가 잠시 down 이면 어떻게 되나?
Webhook retry — 제공자별
- Stripe — 최대 3 일, 지수 백오프 (5 초→8 시간).
- GitHub — 8 시간 내 5 회 시도 후 비활성화.
- Slack — 5 회 시도.
수신 측은 5 초 내 200 응답 권장. 처리는 큐로 비동기. signature 검증 → 200 → 작업 큐 enqueue 패턴이 표준.
Webhook 검증 코드
// Stripe 스타일 (자세히는 [[hmac-webhooks]] 가이드)
import {createHmac, timingSafeEqual} from "crypto";
function verify(body, sigHeader, secret) {
const [t, v1] = parseSignature(sigHeader);
const signed = `${t}.${body}`;
const expected = createHmac("sha256", secret).update(signed).digest("hex");
return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(v1, "hex"));
}HMAC 생성기 에서 같은 알고리즘으로 직접 계산해 보면 서명 형식 직관적.
3. Server-Sent Events — 단방향 스트림
HTTP 위에 만들어진 표준 (W3C). 서버가 클라이언트로 이벤트 텍스트를 스트림. 브라우저 EventSource API 가 자동 처리.
// 서버 (Node.js)
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
setInterval(() => {
res.write(`data: ${JSON.stringify({time: Date.now()})}\n\n`);
}, 1000);
// 클라이언트
const es = new EventSource("/api/stream");
es.onmessage = (e) => {
console.log(JSON.parse(e.data));
};장점:
- 표준 HTTP — 방화벽·프록시·CORS 익숙한 규칙 적용.
- 브라우저 native API — polyfill 0.
- connection 끊기면 자동 재시도 (
retry:디렉티브). - 단방향이라 안전 — 클라이언트가 서버를 못 누름.
단점:
- 단방향 — 클라이언트 → 서버 메시지 X. 양방향 필요 시 WebSocket.
- HTTP/1.1 에서는 같은 호스트에 6 connection 한도. HTTP/2 multiplex 로 해결.
- 모든 환경에서 SSE 지원하는 건 아님 — IE 미지원 (IE 가 죽었지만).
4. WebSocket — 양방향 full-duplex
TCP 위에 만든 별도 프로토콜 (ws://, wss://). HTTP 업그레이드 핸드셰 이크 후 양방향 메시지.
// 서버 (ws library)
import {WebSocketServer} from "ws";
const wss = new WebSocketServer({port: 8080});
wss.on("connection", (ws) => {
ws.on("message", (data) => {
ws.send(`echo: ${data}`);
});
});
// 클라이언트
const ws = new WebSocket("wss://example.com/socket");
ws.onmessage = (e) => console.log(e.data);
ws.send("hello");장점:
- 양방향 즉시. 게임·협업·거래소 등 필수.
- 오버헤드 작음 — 메시지 frame 만.
단점:
- 별도 인프라 — HTTP 캐시·프록시 통과 어려움. CDN edge 일부는 미지원 또는 별도 가격.
- 재연결·인증·heartbeat 직접 구현.
EventSource와 달리 자동 재시도 X. - 확장 = sticky session 또는 broker (Redis pub/sub) 필요.
선택 가이드
Stripe·GitHub 같은 외부 이벤트 받기
Webhook 무조건. polling 은 제공자 API rate limit + 지연 이중 손해. 외부 SaaS 는 webhook 거의 다 지원.
장기 실행 작업 status
Polling (5-10 초). 단순. 작업이 30 초 이내면 polling 충분. 1 분+ 이면 webhook → 사용자 이메일/푸시 또는 SSE 로 진행률 스트림.
대시보드·라이브 차트
SSE. 단방향 + 자동 재시도. WebSocket 까지 갈 필요 0. 메트릭 1 초마다 push.
채팅·협업·게임
WebSocket. 양방향 + 메시지 순서 + 낮은 지연 모두 필요.
제공자 측이 webhook 없을 때
polling 의 사용처. Cron 표현식 분석 이나 Cron 표현식 빌더 로 cron 표현식 만들어 정기 fetch.
Webhook + polling 하이브리드
실무에서 가장 안정적인 패턴:
- 주된 채널: webhook (즉시).
- 백업 cron: 24 시간마다 polling. webhook 실패·누락 감지 (예: 지난 24 시간 결제 ID 가 우리 DB 와 일치하는지).
- 불일치 발견 시 재처리.
webhook 의 단점 (네트워크 일시 단절 시 누락) 을 polling 으로 보완. Stripe 도 공식 권장하는 패턴.
흔한 함정
1. Webhook 처리 동기로 무거운 작업
5 초 초과 시 제공자가 timeout 처리. 응답 빨리 반환 + 큐로 비동기.
2. Webhook 검증 누락
endpoint URL 만 알면 누구나 가짜 이벤트 보낼 수 있음. 결제 사기. HMAC 검증기 constant-time 비교 필수.
3. Polling 간격 너무 짧음
1 초 polling = 분당 60 req × 사용자 수. rate limit 또는 비용 폭발. WebSocket / SSE 로 전환 고려.
4. WebSocket 의 sticky session 무시
load balancer 가 매 connection 다른 서버로 라우팅 → 메시지 broadcast 깨짐. Redis pub/sub 또는 sticky session 으로 해결.
5. SSE / WebSocket 의 inactive timeout
프록시·CDN 이 일정 시간 idle 후 connection 끊음. heartbeat (15-30 초 주기 ping) 필수.
6. Webhook URL 노출
URL 자체가 비밀처럼 동작하면 안 됨. 서명 검증 + URL 추정 어렵게 + IP 화이트리스트 (제공자가 IP 공개 시).
7. HTTP 상태 코드 응답 의미 오해
webhook 수신 시 200 = "받았다" 이면 충분. 처리 결과는 별도. 4xx / 5xx 반환 시 제공자가 retry 트리거.
요약
- 외부 SaaS 이벤트 = webhook (지연 즉시, 비용 최소).
- 폴링은 단순함의 가치만큼 비용. 사용자별 5 초 이상 권장.
- 단방향 라이브 = SSE. 양방향 = WebSocket.
- Webhook 은 항상 서명 검증 + 5 초 내 응답 + 비동기 처리.
- webhook + polling 하이브리드가 production 안정성 표준.