들어가며

두 서비스 간에 연동을 설정해본 적이 있다면 — 예를 들어 한 시스템의 데이터를 CRM이나 Google 스프레드시트에 자동으로 전달하기 위해서 — "웹훅(webhook)"이라는 용어를 접해봤을 가능성이 큽니다. 이는 Slack 알림부터 온라인 쇼핑몰의 주문 동기화에 이르기까지, 현대 자동화의 상당 부분을 조용히 뒷받침하는 기술 중 하나입니다. 이 글에서는 웹훅이 무엇인지 쉬운 말로 설명하고, 일반적인 API 요청과 어떻게 다른지, 그리고 Lix.li의 웹훅을 이용해 단축 링크 클릭 알림을 실제로 설정하는 방법을 살펴보겠습니다.

웹훅이란 무엇인가, 쉽게 설명하면

웹훅은 특정 이벤트가 발생하는 순간, 한 서비스에서 다른 서비스로 데이터를 자동으로 전달하는 방식입니다. 여러분의 시스템이 "새로운 게 생겼나요?"라고 계속 물어보는 대신, 이벤트가 발생하는 즉시 서비스 스스로가 여러분의 서버로 데이터를 보내줍니다. 이 차이를 이해하는 가장 쉬운 방법은 우편에 비유하는 것입니다.

  • 일반적인 API 요청은 편지가 왔는지 확인하려고 직접 우체통까지 반복해서 다녀오는 것과 같습니다.
  • 웹훅은 배송 서비스를 신청해두는 것과 같습니다. 편지가 준비되는 즉시 자동으로 집 앞까지 배달됩니다. 기술적으로 웹훅은 특정 이벤트가 발생했을 때 — 주문 결제, 작업 상태 변경, 또는 Lix.li의 경우 단축 링크 클릭 등 — 한 서버가 다른 서버의 미리 지정된 URL로 보내는 일반적인 HTTP 요청(대개 POST)일 뿐입니다.

웹훅이 왜 필요한가

웹훅은 매우 구체적인 문제를 해결합니다. 바로 외부 서비스를 계속 "폴링(polling)"하지 않고도 최신 데이터를 얻는 방법입니다. 웹훅이 없다면, 새로운 이벤트를 알기 위해 API에 정기적으로 요청을 보내야 합니다. 1분마다, 5분마다 새로운 것이 생겼는지 매번 확인해야 하죠. 이는 양쪽 서버 모두에 불필요한 부하를 주며, 실제 이벤트 발생 시점과 이를 알게 되는 시점 사이에 항상 지연이 생깁니다. 웹훅은 이 논리를 뒤집습니다. 무언가 발생하면 서비스가 직접 여러분에게 알려줍니다. 이는 다음과 같은 경우에 특히 유용합니다.

  • 비즈니스 프로세스 자동화 — 예를 들어 새로운 리드를 CRM에 자동으로 기록하는 경우.
  • 분석 도구와의 연동 — 클릭 데이터를 자체 추적 시스템으로 전달하는 경우.
  • 봇과 알림 — Telegram 봇이나 팀의 Slack 채널로 이벤트를 전송하는 경우.
  • 데이터 동기화 — Google 스프레드시트, 대시보드, 또는 내부 시스템을 실시간으로 업데이트하는 경우.

웹훅은 어떻게 작동하는가: Lix.li 사례

Lix.li에서 웹훅을 사용하면 단축 링크의 클릭 정보를 여러분의 서버로 직접, 자동으로 받을 수 있습니다. 서비스의 API를 계속 조회할 필요가 없습니다. CRM, 분석 시스템, Google 스프레드시트, Telegram 봇, 또는 그 밖의 자동화 시스템으로 이벤트를 전달하고 싶다면 유용합니다.

어떻게 구성되어 있는가

  • 클릭은 하나씩 전송되지 않고, 몇 분마다 모아서 일괄(batch)로 전달됩니다. 이는 여러분의 서버와 Lix.li 서버 양쪽의 부하를 줄여줍니다.
  • 전달은 거의 실시간으로 이루어지지만 즉시는 아닙니다. 웹훅은 각각의 클릭에 즉각적으로 반응해야 하는 시나리오가 아니라, 몇 분 정도의 지연이 허용되는 시나리오를 위해 설계되었습니다.
  • 전달은 "최소 한 번(at least once)" 원칙으로 보장됩니다. 전송 중 네트워크 장애가 발생하면 배치가 다시 도착할 수 있습니다. 그래서 모든 이벤트에는 고유한 event_id가 있으며, 여러분 쪽에서 이를 기준으로 중복을 걸러내야 합니다.

대시보드에서 웹훅 설정하기

웹훅 기능은 Premium 요금제에서 이용할 수 있습니다. 설정은 몇 단계만 거치면 됩니다.

  1. 대시보드에서 "웹훅" 섹션을 열고 **"추가"**를 클릭합니다.
  2. 다음을 입력합니다.
    • 수신 URL — 들어오는 요청을 받을 여러분 서버의 주소(예: https://api.yoursite.com/webhooks/lix)
    • 범위 — 모든 링크, 특정 그룹, 또는 하나의 링크에 대한 이벤트만 보낼지 선택
    • 방문자의 IP 주소를 이벤트 데이터에 포함할지 여부(개인정보이므로 기본값은 비활성화)
  3. 웹훅을 생성한 직후, 딱 한 번만 표시되는 비밀 키가 나타납니다. 반드시 저장해 두세요. 이는 들어오는 요청의 진위를 확인하는 데 사용됩니다.
  4. **"테스트"**를 클릭하면 서비스가 테스트 이벤트를 전송하고, 여러분의 서버가 올바르게 응답했는지 보여줍니다. Lix.li 대시보드에서 웹훅을 생성하는 과정과 설정된 웹훅 목록

여러분의 서버로 전달되는 내용

모든 요청은 POST 방식으로, application/json 형식의 본문과 함께 전송됩니다. 데이터 자체와 함께 몇 가지 서비스 헤더도 포함됩니다.

헤더 용도
X-Lix-Signature sha256= 형식의 본문 서명 — 진위 확인에 사용됩니다.
X-Lix-Timestamp 요청 전송 시각을 Unix 타임스탬프로 표시합니다.
X-Lix-Delivery 고유한 전달 식별자입니다.
User-Agent Lix-Webhooks/1.0으로 설정됩니다.
요청 본문에는 이벤트 배치 자체가 담겨 있습니다.
{
  "delivery_id": "0f3b9c2e-6a1d-4e88-9d5a-2f8c1b7a4e10",
  "event_type": "redirects.batch",
  "sent_at": "2026-06-01T12:05:00Z",
  "window": {
    "from": "2026-06-01T12:00:00Z",
    "to":   "2026-06-01T12:03:30Z"
  },
  "count": 2,
  "truncated": false,
  "events": [
    {
      "event_id":   "2ef7bde608ce5404e97d5f042f95f89f1c232871",
      "link_id":    12345,
      "datetime":   "2026-06-01T12:01:00Z",
      "country":    "US",
      "city":       "Boston",
      "browser":    "Chrome",
      "os":         "Windows",
      "device":     "Desktop",
      "ref_domain": "google.com",
      "group_id":   null,
      "is_bot":     false
    }
  ]
}

배치 안의 각 이벤트에는 링크 ID, UTC 기준 클릭 시각, 국가, 도시, 브라우저, 운영체제, 기기 종류, 클릭이 발생한 출처 도메인, 그룹 ID(설정된 경우), 그리고 봇 여부를 나타내는 플래그가 포함됩니다. 방문자의 IP 주소는 웹훅 설정 시 해당 옵션을 명시적으로 활성화한 경우에만 데이터에 포함됩니다. 이벤트가 매우 많이 쌓인 경우, 해당 배치는 truncated: true로 표시될 수 있습니다. 이는 나머지 데이터가 다음 전달 때 도착한다는 의미이며, 데이터가 손실되지는 않습니다.

요청의 진위를 확인하는 방법

여러분 서버의 URL은 기술적으로 어디서든 요청을 받을 수 있기 때문에, 그 요청이 실제로 Lix.li에서 온 것이며 위조된 것이 아닌지 확인하는 것이 중요합니다. 이를 위한 것이 바로 X-Lix-Signature 헤더에 담긴 서명입니다. 검증 원리는 다음과 같습니다. 요청의 "원본"(가공되지 않은) 본문을 가져와, 여러분의 비밀 키를 사용해 HMAC-SHA256 해시를 계산한 뒤, 그 결과를 헤더에 담긴 값과 비교합니다. 타이밍 공격을 방지하기 위해 이 비교는 안전한 방식(고정 시간 비교)으로 이루어져야 합니다. PHP 예시:

$raw    = file_get_contents('php://input');
$secret = 'your_secret';
$expected = 'sha256=' . hash_hmac('sha256', $raw, $secret);
if (!hash_equals($expected, $_SERVER['HTTP_X_LIX_SIGNATURE'] ?? '')) {
    http_response_code(401);
    exit;
}

Node.js(Express) 예시:

const crypto = require('crypto');
// 원본(RAW) 본문을 가져오는 것이 중요합니다: app.use(express.raw({ type: 'application/json' }))
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(req.body).digest('hex');
const got = req.header('X-Lix-Signature') || '';
if (expected.length !== got.length ||
    !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(got))) {
  return res.sendStatus(401);
}

Python(Flask) 예시:

import hmac, hashlib
raw = request.get_data()  # 원본 바이트
expected = 'sha256=' + hmac.new(secret.encode(), raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get('X-Lix-Signature', '')):
    return '', 401

수신 서버가 갖춰야 할 요건

웹훅이 안정적으로 작동하려면 여러분의 서버가 몇 가지 규칙을 따라야 합니다.

  1. 2xx 코드로 응답할 것. 다른 코드나 시간 초과는 모두 실패로 간주되어 전달이 재시도됩니다.
  2. 빠르게 응답할 것. 응답 시간은 단 몇 초뿐입니다. 요청 핸들러 안에서 데이터를 동기적으로 처리하지 마세요. 올바른 방법은, 받은 데이터를 빠르게 저장(큐나 데이터베이스 등에)한 뒤 2xx를 반환하고, 이후 별도로 처리하는 것입니다.
  3. event_id로 중복을 제거할 것. 재시도 메커니즘 때문에 동일한 이벤트가 가끔 두 번 도착할 수 있습니다.
  4. 멱등성(idempotent)을 유지할 것. 같은 배치를 다시 처리해도 데이터가 손상되지 않아야 합니다.
  5. 모든 수신 요청에서 항상 서명을 확인할 것.
  6. HTTPS를 사용할 것. 로컬 및 내부 주소는 웹훅 수신에 지원되지 않습니다.

오류가 발생하면 어떻게 되는가

여러분의 서버가 2xx 코드로 응답하지 않으면, Lix.li는 재시도 간격을 점점 늘려가며 전달을 다시 시도합니다. 수신 서버가 오랫동안 전혀 응답하지 않으면(연속으로 여러 번 실패), 무의미한 전송을 막기 위해 웹훅이 자동으로 일시 중지됩니다. 이는 대시보드의 전달 기록에 표시되며, 여러분 쪽에서 문제를 해결한 후 웹훅을 다시 켤 수 있습니다.

대시보드에서 웹훅 관리하기

설정된 각 웹훅에 대해 다음 작업을 수행할 수 있습니다.

  • 테스트 — 테스트 이벤트 하나를 보내고 결과(성공 또는 서버의 구체적인 응답 코드)를 즉시 확인합니다.
  • 일시 중지 / 재개 — 이벤트 전달을 일시적으로 중단하거나 다시 시작합니다.
  • 비밀 키 교체 — 기존 키 대신 새 키를 생성합니다. 이전 키는 즉시 작동을 멈추므로, 여러분 쪽에서도 바로 업데이트해야 합니다.
  • 삭제 — 웹훅을 완전히 제거합니다.
  • 전달 기록 — 최근 전달 내역을 상태, HTTP 응답 코드, 배치 내 이벤트 수, 전송 시각과 함께 확인합니다.

자주 묻는 질문

이벤트는 얼마나 빨리 도착하나요? 배치 단위로, 대략 몇 분마다 도착하며 처리에 약간의 지연이 있습니다. 즉시 푸시되는 방식이 아니라 거의 실시간에 가까운 시나리오입니다. 같은 이벤트가 두 번 도착할 수 있나요? 네트워크 장애 후 재전달이 이루어지면 그럴 수 있습니다. 그렇기 때문에 여러분 쪽에서 event_id 필드로 이벤트 중복을 제거하는 것이 중요합니다. 이벤트의 엄격한 순서가 보장되나요? 하나의 배치 안에서는 이벤트가 시간 순서대로 오지만, 서로 다른 배치 간에는 엄격한 순서가 보장되지 않습니다. 정렬이 필요하다면 각 이벤트의 datetime 필드를 기준으로 하세요. 트래픽이 매우 많을 때는 어떻게 되나요? 한 구간에 너무 많은 이벤트가 쌓이면 해당 배치는 truncated: true로 표시되고, 나머지 데이터는 다음 전달 때 도착합니다. 이 과정에서 데이터 손실은 없습니다. HTTPS가 반드시 필요한가요? 네, 수신 주소는 반드시 https://로 시작해야 합니다. 로컬 및 내부 주소는 허용되지 않습니다. 하나의 링크나 링크 그룹에 대한 이벤트만 받을 수 있나요? 네, 웹훅을 생성할 때 계정의 전체 링크 대신 "링크 1개" 또는 "그룹" 범위를 선택할 수 있습니다.

PHP로 작성한 전체 핸들러 예시

아래는 서명을 확인하고, 빠르게 응답하며, 중복 방지 기능을 갖춰 이벤트를 처리하는 최소한의 실제 작동 가능한 핸들러 예시입니다.