Webhook 서비스로 로컬호스트 API 테스트하는 방법

Ashley Innocent

Ashley Innocent

28 January 2026

Webhook 서비스로 로컬호스트 API 테스트하는 방법

외부 서비스로부터 웹훅 또는 콜백을 받아야 하는 로컬호스트 API를 테스트하려면 로컬 개발 서버를 인터넷에 일시적으로 노출해야 합니다. ngrok, NPort, Cloudflare Tunnel 등과 같은 터널링 서비스는 로컬호스트에 공개 URL을 부여하는 보안 연결을 생성합니다.

💡
이 가이드의 웹훅 테스트 워크플로를 따라하려면 Apidog를 다운로드하세요. 이 가이드는 올바른 도구를 선택하고, 터널링을 설정하고, Apidog를 사용하여 웹훅을 효과적으로 테스트하며, 인증, 속도 제한, 디버깅과 같은 일반적인 문제를 처리하는 방법을 다룹니다.
button

로컬호스트 터널링이 필요한 이유

서드파티 서비스와 통합되는 API를 구축하고 있습니다. 모든 것이 노트북에서 잘 작동하고, 엔드포인트는 올바르게 응답하고, 데이터는 원활하게 흐릅니다. 그러다가 Stripe, GitHub, Twilio 또는 기타 외부 서비스로부터의 웹훅 콜백을 테스트하려고 합니다.

문제: 외부 서비스가 localhost:3000에 도달할 수 없습니다. 개발 서버가 인터넷에서 접근할 수 없습니다.

이것이 워크플로우를 방해하는 일반적인 시나리오:

1. 웹훅 테스트

Stripe와 같은 서비스는 결제 확인을, GitHub는 저장소 이벤트를, Slack은 상호 작용 이벤트를 모두 API에 대한 POST 요청으로 보냅니다. 개발 중에는 이러한 서비스가 웹훅을 보낼 공개 URL이 필요합니다.

2. OAuth 콜백 URL

“Google로 로그인”, “GitHub로 로그인” 또는 기타 OAuth 플로우를 구현할 때, 인증 제공업체는 권한 부여 코드를 사용하여 사용자를 애플리케이션으로 다시 리디렉션합니다. 리디렉션 URL은 공개적으로 접근 가능해야 하며 제공업체에 등록한 것과 일치해야 합니다.

3. 서드파티 API 통합

일부 API는 비동기 작업을 위한 콜백 URL을 요구합니다. 예를 들어, 동영상 트랜스코딩 서비스는 처리가 완료되면 API에 알리거나, 결제 처리기는 거래를 확인합니다.

4. 모바일 앱 개발

동일 네트워크의 모바일 장치에서 API를 테스트하면 모바일 앱이 localhost를 확인할 수 없기 때문에 종종 실패합니다. 터널은 어떤 장치에서도 작동하는 URL을 제공합니다.

5. 클라이언트 데모

때로는 진행 중인 작업을 클라이언트나 이해 관계자에게 보여줘야 할 때가 있습니다. 작은 변경 사항마다 스테이징에 배포하면 반복 속도가 느려집니다. 임시 공개 URL을 통해 클라이언트가 개발 환경을 테스트할 수 있습니다.

로컬호스트 터널링 작동 방식

터널링 서비스는 클라우드 서버와 로컬 머신 사이에 보안 연결을 생성합니다.

외부 서비스 → 터널링 서비스 (공개 URL) → 보안 연결 → 로컬호스트:3000

진행 과정:

  1. 로컬 포트를 가리키는 터널 클라이언트를 머신에서 시작합니다.
  2. 클라이언트가 터널링 서비스의 클라우드 인프라에 연결합니다.
  3. 서비스가 공개 URL(예: https://abc123.ngrok.io)을 할당합니다.
  4. 해당 공개 URL로 들어오는 요청은 암호화된 연결을 통해 로컬호스트로 전달됩니다.
  5. 로컬 서버는 클라이언트로부터 직접 온 것처럼 요청을 받습니다.
  6. 응답은 터널을 통해 요청자에게 다시 흐릅니다.

이 과정은 투명하게 이루어집니다. 로컬 서버는 자신이 터널 뒤에 있다는 것을 알 필요가 없습니다.

인기 터널링 서비스 비교

2026년에 가장 인기 있는 옵션들과 그 장점 및 한계점은 다음과 같습니다.

ngrok (가장 인기 많음)

가장 적합한 대상: 확고한 프로젝트, 안정성을 원하는 팀

ngrok http 3000

장점:

단점:

무료 티어:

유료 요금제: 월 $8-$20

NPort (떠오르는 무료 대안)

가장 적합한 대상: 구독 비용을 피하려는 개발자

nport start 3000

장점:

단점:

무료 티어:

이것은 개발자들이 지속적인 비용 없이 ngrok 대안을 찾으면서 Dev.to에서 인기를 얻고 있는 도구입니다.

Cloudflare Tunnel (프로덕션 환경에 가장 적합)

가장 적합한 대상: 이미 Cloudflare를 사용하는 팀, 장기 실행 터널

cloudflared tunnel --url http://localhost:3000

장점:

단점:

무료 티어:

Localtunnel (가장 간단)

가장 적합한 대상: 빠른 일회성 테스트, 설치 불필요

npx localtunnel --port 3000

장점:

단점:

무료 티어:

Tailscale Funnel (팀에 가장 적합)

가장 적합한 대상: 비공개 팀 공유, 보안 데모

tailscale serve https / http://localhost:3000
tailscale funnel 443 on

장점:

단점:

무료 티어:

비교표

기능ngrokNPortCloudflare TunnelLocaltunnelTailscale
가격무료/$10+무료무료무료무료/유료
세션 제한2시간없음없음없음없음
사용자 지정 도메인유료무료아니요
요청 검사기기본아니요아니요아니요
설정 복잡도낮음낮음중간매우 낮음중간
신뢰성우수좋음우수나쁨우수
가장 적합한 대상프로덕션 테스트비용에 민감한 개발자엔터프라이즈빠른 테스트팀 공유

첫 로컬호스트 터널 설정하기

가장 일반적인 도구로 설정을 진행해 보겠습니다. Node.js Express API를 예시로 사용하지만, 이 방법은 어떤 로컬 서버에도 적용됩니다.

예시: 로컬 API 서버

// server.js
const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook', (req, res) => {
  console.log('Webhook received:', req.body);
  res.json({ received: true });
});

app.get('/health', (req, res) => {
  res.json({ status: 'healthy' });
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

옵션 1: ngrok 사용

단계 1: ngrok 설치

# macOS
brew install ngrok

# Windows (Chocolatey를 통해)
choco install ngrok

# Linux
curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | \
  sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null && \
  echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | \
  sudo tee /etc/apt/sources.list.d/ngrok.list && \
  sudo apt update && sudo apt install ngrok

단계 2: 인증 (선택 사항이지만 권장)

ngrok config add-authtoken YOUR_AUTH_TOKEN

단계 3: 터널 시작

ngrok http 3000

출력:

Session Status                online
Account                       you@example.com (Plan: Free)
Version                       3.5.0
Region                        United States (us)
Forwarding                    https://abc123.ngrok.io -> http://localhost:3000

이제 API는 https://abc123.ngrok.io에서 접근할 수 있습니다.

단계 4: 테스트

curl https://abc123.ngrok.io/health
# {"status":"healthy"}

옵션 2: NPort 사용 (무료 대안)

단계 1: NPort 설치

npm install -g nport-cli
# 또는
curl -sSL https://nport.io/install.sh | bash

단계 2: 터널 시작

nport start 3000 --subdomain myapi

출력:

✓ Tunnel started successfully
Public URL: https://myapi.nport.io
Local URL:  http://localhost:3000

단계 3: 테스트

curl https://myapi.nport.io/health
# {"status":"healthy"}

옵션 3: Cloudflare Tunnel 사용

단계 1: cloudflared 설치

# macOS
brew install cloudflare/cloudflare/cloudflared

# Linux
wget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared-linux-amd64.deb

단계 2: 빠른 터널 (가입 불필요)

cloudflared tunnel --url http://localhost:3000

출력:

2026-01-27T12:00:00Z INF Your quick tunnel is: https://xyz789.trycloudflare.com

영구 터널의 경우 (Cloudflare 계정 필요):

# 로그인
cloudflared tunnel login

# 터널 생성
cloudflared tunnel create myapi

# 구성 및 실행
cloudflared tunnel --config config.yml run myapi

Apidog로 웹훅 테스트하기

이제 로컬호스트가 공개적으로 접근 가능해졌으므로, Apidog를 사용하여 웹훅을 체계적으로 테스트해 보겠습니다.

터널링 + Apidog를 결합하는 이유?

터널링은 접근을 해결하고; Apidog는 검증을 해결합니다:

Apidog에서 웹훅 테스트 설정

단계 1: API 가져오기 또는 생성

  1. Apidog 열기

2. 새 프로젝트 생성

3. 웹훅 엔드포인트 추가:

단계 2: 환경 변수 구성

두 가지 환경 설정:

개발 (터널링):

{
  "base_url": "https://abc123.ngrok.io"
}

프로덕션:

{
  "base_url": "https://api.yourapp.com"
}

이를 통해 동일한 엔드포인트를 로컬 및 프로덕션 환경에서 한 번의 클릭으로 테스트할 수 있습니다.

단계 3: 테스트 시나리오 생성

웹훅이 도착할 때 어떤 일이 발생하는지 테스트:

예시: Stripe 결제 웹훅 테스트

// 요청 본문
{
  "type": "payment_intent.succeeded",
  "data": {
    "object": {
      "id": "pi_test123",
      "amount": 2000,
      "currency": "usd",
      "status": "succeeded"
    }
  }
}

Apidog의 어설션:

  1. 상태 코드 200
  2. 응답에 received: true 포함
  3. 응답 시간 < 1000ms
  4. Content-Type은 application/json

단계 4: 서드파티 서비스 시뮬레이션

Stripe 또는 GitHub에서 실제 웹훅을 트리거하는 대신, Apidog에서 시뮬레이션합니다:

  1. 서비스 문서에서 웹훅 페이로드 예시 복사
  2. 다양한 시나리오 (성공, 실패, 엣지 케이스)로 테스트 케이스 생성
  3. 터널링된 로컬호스트에 대해 모든 시나리오 실행
  4. API가 각 케이스를 올바르게 처리하는지 확인

OAuth 콜백 테스트

시나리오: "Google로 로그인"을 구현 중입니다.

단계 1: 사용자 지정 서브도메인으로 터널 시작

ngrok http 3000 --subdomain myapp
# URL: https://myapp.ngrok.io

단계 2: Google Console에서 OAuth 리디렉션 구성

콜백 URL 설정: https://myapp.ngrok.io/auth/google/callback

단계 3: Apidog에서 플로우 테스트

  1. 권한 부여 URL을 얻기 위해 /auth/google로 요청
  2. 수동 또는 프로그래밍 방식으로 리디렉션 따르기
  3. 콜백이 권한 부여 코드를 받는지 확인
  4. 토큰 교환이 올바르게 작동하는지 확인

단계 4: 토큰 저장 유효성 검사

Apidog를 사용하여:

일반적인 사용 사례

1. 결제 웹훅 테스트 (Stripe, PayPal)

과제: 결제 제공업체는 성공적인 결제, 환불, 분쟁과 같은 이벤트에 대해 웹훅을 보냅니다.

해결책:

# 터널 시작
ngrok http 3000

# Stripe 대시보드에서 웹훅 URL 구성
# https://abc123.ngrok.io/webhook/stripe

# Stripe CLI를 사용하여 테스트 웹훅 전달
stripe listen --forward-to localhost:3000/webhook/stripe

# 테스트 이벤트 트리거
stripe trigger payment_intent.succeeded

Apidog로 테스트:

2. Slack/Discord 봇 명령 테스트

과제: 채팅 플랫폼은 사용자가 버튼을 클릭하거나 명령을 실행할 때 상호 작용 이벤트를 보냅니다.

해결책:

# 터널 시작
nport start 3000 --subdomain myslackbot

# Slack API에서 구성:
# Interactivity URL: https://myslackbot.nport.io/slack/interactions
# Slash Commands: https://myslackbot.nport.io/slack/commands

Apidog로 테스트:

3. SMS/음성 웹훅 테스트 (Twilio)

과제: Twilio는 SMS가 도착하거나 음성 통화가 수신될 때 웹훅을 보냅니다.

해결책:

cloudflared tunnel --url http://localhost:3000

TwiML 웹훅이 터널 URL을 가리키도록 구성합니다.

Apidog로 테스트:

4. 모바일 앱 API 테스트

과제: 실제 장치 또는 에뮬레이터에서 API 테스트.

localhost의 문제점:

// 모바일 장치에서는 실패
fetch('http://localhost:3000/api/users')

터널을 이용한 해결책:

// 어디서든 작동
fetch('https://myapi.ngrok.io/api/users')

Apidog로 테스트:

  1. 터널링된 기본 URL로 API 문서 생성
  2. 모바일 팀과 공유
  3. 모바일 개발자는 실시간 개발 서버를 대상으로 테스트할 수 있습니다.
  4. 준비가 되면 스테이징/프로덕션 URL로 전환

5. GitHub/GitLab 웹훅 테스트

과제: 저장소 웹훅 (푸시, 풀 리퀘스트, 이슈)을 로컬에서 테스트.

해결책:

# 터널 시작
ngrok http 4000

# GitHub 저장소 설정에서 구성:
# Webhook URL: https://abc123.ngrok.io/github/webhook
# Content type: application/json
# Events: Push, Pull requests

Apidog로 테스트:

보안 모범 사례

로컬호스트를 인터넷에 노출하는 것은 보안 위험을 초래합니다. 다음 관행을 따르세요:

1. HTTPS만 사용

모든 터널링 서비스는 기본적으로 HTTPS를 제공합니다. 터널에 일반 HTTP를 사용하지 마세요:

# 좋음
ngrok http 3000
# https://abc123.ngrok.io 생성

# 나쁨 (이렇게 하지 마세요)
ngrok http --scheme=http 3000

2. 웹훅 서명 검증 구현

들어오는 웹훅을 맹목적으로 신뢰하지 마세요. 서명을 검증하세요:

const crypto = require('crypto');

function verifyStripeSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

app.post('/webhook/stripe', (req, res) => {
  const signature = req.headers['stripe-signature'];

  if (!verifyStripeSignature(req.body, signature, process.env.STRIPE_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  // 웹훅 처리
});

3. 기본 인증으로 접근 제한

터널에 인증 추가:

# 기본 인증이 있는 ngrok
ngrok http 3000 --auth="username:password"

# 기본 인증이 있는 NPort
nport start 3000 --auth username:password

이제 요청에 자격 증명이 필요합니다:

curl -u username:password https://abc123.ngrok.io/webhook

4. 환경별 비밀 사용

웹훅 비밀 또는 API 키를 커밋하지 마세요:

// .env.development
STRIPE_WEBHOOK_SECRET=whsec_test_abc123
WEBHOOK_TUNNEL_URL=https://abc123.ngrok.io

// .env.production
STRIPE_WEBHOOK_SECRET=whsec_live_xyz789
WEBHOOK_URL=https://api.yourapp.com

5. 터널 접근 모니터링

의심스러운 활동을 감시하려면 요청 검사기를 사용하세요:

# ngrok는 웹 인터페이스를 제공합니다:
http://localhost:4040

# 모든 요청, 응답, 재실행 공격 보기

6. 터널 기간 제한

터널을 무한정 실행하지 마세요:

# 1시간 후 터널 자동 만료
ngrok http 3000 --session-duration 1h

7. 요청 소스 유효성 검사

들어오는 IP 주소를 확인하거나 허용 목록을 사용하세요:

const allowedIPs = [
  '192.0.2.1',  // Stripe 웹훅 IP
  '198.51.100.0/24'
];

app.use('/webhook', (req, res, next) => {
  const clientIP = req.ip;

  if (!allowedIPs.includes(clientIP)) {
    return res.status(403).send('Forbidden');
  }

  next();
});

일반적인 문제 해결

문제 1: 터널 URL이 세션마다 변경됨

문제: 무료 ngrok 터널은 다시 시작할 때마다 변경되는 무작위 URL을 사용합니다. 이전 URL로 구성된 웹훅은 작동하지 않습니다.

해결책:

  1. 고정 URL을 위해 유료 요금제 사용:
ngrok http 3000 --domain=myapp.ngrok.app
  1. 무료 사용자 지정 서브도메인이 있는 NPort로 전환:
nport start 3000 --subdomain myapp
# 항상 https://myapp.nport.io
  1. 터널 시작 시 API를 통해 웹훅을 프로그래밍 방식으로 업데이트

문제 2: 웹훅 시간 초과

문제: 로컬 서버가 응답하는 데 너무 오래 걸립니다. Slack과 같은 서비스는 3초 이내에 응답을 요구합니다.

해결책:

비동기적으로 처리:

app.post('/webhook', async (req, res) => {
  // 즉시 승인
  res.json({ received: true });

  // 백그라운드에서 처리
  processWebhookAsync(req.body).catch(console.error);
});

async function processWebhookAsync(data) {
  // 여기서 느린 작업 수행 (데이터베이스, 외부 API 등)
  await heavyProcessing(data);
}

테스트 시나리오에서 공격적인 시간 초과 제한을 설정하여 Apidog로 시간 초과를 테스트하세요.

문제 3: 브라우저에서 CORS 오류 발생

문제: 프론트엔드가 터널 URL로 요청을 보낼 때 CORS 오류가 발생합니다.

해결책:

CORS 헤더 구성:

const cors = require('cors');

app.use(cors({
  origin: [
    'http://localhost:3001',  // 프론트엔드 개발 서버
    'https://abc123.ngrok.io'  // 터널 URL
  ],
  credentials: true
}));

문제 4: 무료 티어의 속도 제한

문제: 무료 터널에는 연결 제한이 있습니다 (ngrok: 분당 40회).

해결책:

  1. 개별 테스트를 빠르게 실행하는 대신 Apidog에서 요청을 일괄 테스트
  2. 다른 서비스를 위해 여러 터널 사용
  3. 테스트가 많다면 유료 티어로 업그레이드
  4. Cloudflare Tunnel 또는 NPort와 같은 무제한 서비스로 전환

문제 5: 터널이 자주 끊김

문제: 네트워크 불안정으로 인해 터널이 끊어집니다.

해결책:

systemd/pm2를 사용하여 자동 재시작:

# systemd 서비스 생성
sudo nano /etc/systemd/system/ngrok.service
[Unit]
Description=ngrok tunnel
After=network.target

[Service]
Type=simple
User=youruser
WorkingDirectory=/home/youruser
ExecStart=/usr/local/bin/ngrok http 3000
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
sudo systemctl enable ngrok
sudo systemctl start ngrok

문제 6: 특정 네트워크에서 터널에 도달할 수 없음

문제: 기업 방화벽 또는 제한적인 네트워크가 터널 트래픽을 차단합니다.

해결책:

  1. Cloudflare Tunnel 사용 (거의 차단되지 않음)
  2. 가까운 터널 지역으로 전환:
ngrok http 3000 --region eu
  1. 공개 터널 대신 비공개 네트워크에 Tailscale 사용

고급 패턴

패턴 1: 다중 포트 터널링

여러 서비스를 동시에 노출:

# 터미널 1: API 서버
ngrok http 3000

# 터미널 2: 프론트엔드 개발 서버
ngrok http 3001

# 터미널 3: 웹훅 워커
ngrok http 3002

또는 ngrok 구성 파일 사용:

# ~/.ngrok2/ngrok.yml
tunnels:
  api:
    proto: http
    addr: 3000
  frontend:
    proto: http
    addr: 3001
  worker:
    proto: http
    addr: 3002
ngrok start --all

패턴 2: 터널 + Docker Compose

# docker-compose.yml
version: '3'
services:
  api:
    build: .
    ports:
      - "3000:3000"

  ngrok:
    image: ngrok/ngrok:latest
    command:
      - "http"
      - "api:3000"
    environment:
      NGROK_AUTHTOKEN: ${NGROK_AUTHTOKEN}
docker-compose up

패턴 3: 동적 터널 URL 주입

터널 URL로 앱 자동 업데이트:

// start-tunnel.js
const ngrok = require('ngrok');
const fs = require('fs');

(async function() {
  const url = await ngrok.connect(3000);
  console.log(`Tunnel started: ${url}`);

  // .env 파일 업데이트
  fs.appendFileSync('.env', `\nTUNNEL_URL=${url}\n`);

  // Stripe 웹훅 업데이트
  await updateStripeWebhook(url);
})();

패턴 4: 여러 환경으로 요청 전달

개발, 스테이징, 프로덕션 환경에 대해 동일한 웹훅 테스트:

// webhook-multiplexer.js
app.post('/webhook', async (req, res) => {
  const environments = [
    'http://localhost:3000',
    'https://staging.api.com',
    'https://api.yourapp.com'
  ];

  // 모든 환경으로 전달
  const results = await Promise.all(
    environments.map(env =>
      fetch(`${env}/webhook`, {
        method: 'POST',
        headers: req.headers,
        body: JSON.stringify(req.body)
      })
    )
  );

  res.json({ forwarded: results.length });
});

결론

웹훅 또는 콜백을 수신하는 로컬호스트 API를 테스트할 때 변경 사항이 있을 때마다 스테이징에 배포할 필요가 없습니다. 터널링 서비스는 외부 서비스가 개발 환경에 도달할 수 있도록 임시 공개 URL을 생성합니다.

어떤 도구든 무료 티어부터 시작하세요. 웹훅 테스트가 일상적인 워크플로우의 일부가 된다면, 고정 URL 및 추가 기능을 위한 유료 요금제를 고려해 보세요. 하지만 대부분의 개발자에게는 무료 터널링 서비스와 Apidog의 API 테스트 기능을 결합하는 것으로 로컬호스트 API를 효과적으로 테스트하는 데 필요한 모든 것을 제공합니다.

button

Apidog에서 API 설계-첫 번째 연습

API를 더 쉽게 구축하고 사용하는 방법을 발견하세요