Skip to content
언어: 한국어

개발자를 위한 API

웹훅: 이벤트, 페이로드, 서명

2026년 11월 1일 업데이트 5분 분량

대시보드에서 웹훅을 설정하고, 전송을 받고, HMAC 서명을 검증하는 방법을 curl, Node.js, PHP 예시와 함께 안내합니다.

웹훅은 워크스페이스에서 무언가가 일어나는 즉시(예: 페이지 게시) 내 서버로 HTTP 요청을 보냅니다. 그래서 무언가 바뀌었는지 계속 물어볼 필요가 없습니다. 모든 전송에는 서명이 있어서 서버가 진짜 메시지와 위조된 메시지를 구별할 수 있습니다.

필요한 것

  • 워크스페이스의 소유자 또는 관리자. “웹훅” 메뉴 항목은 이 역할에만 나타남
  • Creator Pro부터. Creator Free에서는 “웹훅은 Creator Pro부터 사용할 수 있습니다.”가 표시됨
  • Creator Pro 최대 5개, Creator Agency 25개, Enterprise 100개(조정 가능)
  • 공개 https 주소로 접속할 수 있는 서버

웹훅은 대시보드에서만 관리합니다. API 키로는 만들거나 바꿀 수 없습니다.

웹훅 만들기

웹훅이 없고 “+ 새 웹훅” 버튼이 있는 웹훅 페이지
워크스페이스의 웹훅 페이지
  1. 내비게이션에서 “웹훅”을 엽니다
  2. “+ 새 웹훅”을 클릭합니다
  3. 대상 URL을 입력하고 이벤트를 고릅니다
  4. 선택: 설명을 입력하고 상태를 고릅니다
  5. “웹훅 만들기”를 클릭합니다
  6. “완료” 단계에서 서명 시크릿(“서명 시크릿 – 지금만 볼 수 있습니다”)을 복사해 서버에 보관합니다. 지금만 표시됩니다
  7. “테스트”로 웹훅을 테스트합니다. 결과는 다음 테스트까지 카드에 남습니다: “전송 성공” 또는 HTTP 코드(또는 “응답 없음”)가 있는 “전송 실패”, 소요 시간, 응답 앞부분
“대상 URL (https://…)”
최대 2048자입니다. https만 가능하며 URL에 인증 정보를 넣을 수 없고, localhost 주소나 .local, .internal, .lan 같은 내부 이름도 쓸 수 없습니다. 이름은 공개 IP 주소를 가리켜야 하며, LinkHarbor가 전송할 때마다 다시 확인합니다.
“이벤트”
이벤트가 하나 이상 필요합니다. 그룹마다 “전체 선택”과 “전체 선택 해제”가 있습니다.
“설명”
선택이며 최대 255자입니다. 예를 들어 웹훅의 용도를 적습니다.
“상태”
“활성” 또는 “사용 중지”입니다. 사용 중지된 웹훅은 전송을 받지 않습니다.

이벤트

이벤트대시보드 표시발생 시점data 내용
page.published페이지 게시대시보드, API, AI 어시스턴트에서 페이지가 게시됨page_id, version, checksum, published_by_user_id(API 키와 AI 어시스턴트는 null)
form.submitted폼 제출방문자가 내 페이지의 폼을 제출함form_id, form_name, submission_id, page_id, block_id, submitted_at, values(필드 키 → 값), consent(동의 키 → true/false)
domain.verified도메인 인증커스텀 도메인이 인증에 성공함domain_id, hostname, verification_method, status
domain.disabled도메인 사용 중지대시보드에서 커스텀 도메인이 사용 중지됨(다운그레이드 시 제외)domain_id, hostname, status
webhook.test–(“테스트” 버튼)“테스트”를 클릭함message(테스트를 시작한 사람의 계정 언어로 된 안내. 비교에 쓰지 말 것)

form.submitted에는 방문자가 제출한 값이 그대로 들어 있지만, IP 주소나 브라우저 정보는 절대 들어 있지 않습니다. 전송은 백그라운드에서 이루어집니다. 방문자는 전송을 기다리지 않으며, 전송이 실패해도 제출은 막히지 않습니다.

전송의 구조

LinkHarbor는 모든 전송을 JSON 본문과 다음 헤더가 있는 POST로 보냅니다.

헤더내용
Content-Typeapplication/json
X-LinkHarbor-Event이벤트 이름. 예: page.published
X-LinkHarbor-Delivery-Id이 전송의 고유 ID(UUID). 본문의 id와 같음
X-LinkHarbor-Timestamp보낸 시각(Unix 초)
X-LinkHarbor-Signaturesha256= 뒤에 16진수 HMAC-SHA256 서명
X-LinkHarbor-Signature-Versionv1
page.published 페이로드 예시
{
  "id": "3f1c2a9e-8d4b-4a51-9f0e-6b7c1d2e3f40",
  "event": "page.published",
  "created_at": "2026-11-01T09:30:12+00:00",
  "data": {
    "page_id": 42,
    "version": 8,
    "checksum": "9b1e5c…",
    "published_by_user_id": 7
  }
}

재전송에는 새 id와 함께 redelivery_of(반복되는 전송의 ID)와 delivery_root(이 체인의 첫 전송 ID) 필드가 붙습니다.

서명 검증하기

모든 웹훅에는 자체 서명 시크릿(16진수 32자)이 있습니다. 웹훅을 만들 때의 마지막 단계와 “시크릿 ↺” 직후에 정확히 한 번만 볼 수 있습니다. 바로 서버에 복사하세요. LinkHarbor는 나중에 다시 보여 주지 않습니다. LinkHarbor는 이 시크릿으로 다음과 같이 서명을 계산합니다.

  1. 메시지를 만듭니다: 타임스탬프, 점, 전송 ID, 점, 원본 본문. 즉 timestamp.delivery_id.body
  2. 서명 시크릿을 키로 이 메시지에 대해 HMAC-SHA256을 계산합니다
  3. 결과를 sha256= 접두어가 붙은 16진수 문자열로 보냅니다

서버는 상수 시간 비교로 서명을 확인하고, 타임스탬프가 300초보다 오래된 전송은 거부하며, 반복을 알아볼 수 있도록 전송 ID를 최소 그 시간 동안 저장해야 합니다. 중요: 서명되는 것은 다시 인코딩한 JSON이 아니라 도착한 그대로의 원본 본문입니다.

Node.js (Express): 웹훅 받기와 검증
import crypto from 'node:crypto';
import express from 'express';

const app = express();
const SECRET = process.env.LH_WEBHOOK_SECRET;
const seen = new Set(); // 운영 환경에서는 만료 기능이 있는 데이터베이스나 Redis

app.post('/webhooks/linkharbor', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.get('X-LinkHarbor-Timestamp') || '';
  const id = req.get('X-LinkHarbor-Delivery-Id') || '';
  const sig = req.get('X-LinkHarbor-Signature') || '';

  if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
    return res.status(400).send('timestamp');
  }

  const expected = 'sha256=' + crypto
    .createHmac('sha256', SECRET)
    .update(ts + '.' + id + '.')
    .update(req.body) // Buffer 형태의 원본 본문
    .digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(sig);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.status(401).send('signature');
  }

  const event = JSON.parse(req.body.toString('utf8'));
  const key = event.delivery_root || event.id; // 같은 체인의 재전송 알아보기
  if (!seen.has(key)) {
    seen.add(key);
    // 여기에서 처리. 가능하면 큐에서 비동기로
  }
  res.sendStatus(200);
});

app.listen(3000);
PHP: 웹훅 받기와 검증
<?php
$secret = getenv('LH_WEBHOOK_SECRET');
$body   = file_get_contents('php://input');
$ts     = $_SERVER['HTTP_X_LINKHARBOR_TIMESTAMP'] ?? '';
$id     = $_SERVER['HTTP_X_LINKHARBOR_DELIVERY_ID'] ?? '';
$sig    = $_SERVER['HTTP_X_LINKHARBOR_SIGNATURE'] ?? '';

if (!ctype_digit($ts) || abs(time() - (int)$ts) > 300) {
    http_response_code(400);
    exit('timestamp');
}

$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $id . '.' . $body, $secret);
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit('signature');
}

$event = json_decode($body, true);
// $event['delivery_root'] ?? $event['id']로 중복 전송을 알아본 뒤
// 처리합니다.
http_response_code(200);

curl로 엔드포인트 테스트하기

이 스크립트는 올바르게 서명된 테스트 전송을 내 서버로 보냅니다. LinkHarbor에서 웹훅을 만들기 전에 유용합니다.

서명된 테스트 전송 시뮬레이션
SECRET="your-signing-secret"
URL="https://example.com/webhooks/linkharbor"
TS=$(date +%s)
ID=$(uuidgen | tr 'A-Z' 'a-z')
BODY='{"id":"'"$ID"'","event":"webhook.test","created_at":"2026-11-01T09:30:12+00:00","data":{"message":"이 웹훅의 테스트 이벤트입니다."}}'
SIG="sha256=$(printf '%s' "$TS.$ID.$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')"

curl -X POST "$URL" \
  -H "Content-Type: application/json" \
  -H "X-LinkHarbor-Event: webhook.test" \
  -H "X-LinkHarbor-Delivery-Id: $ID" \
  -H "X-LinkHarbor-Timestamp: $TS" \
  -H "X-LinkHarbor-Signature: $SIG" \
  -H "X-LinkHarbor-Signature-Version: v1" \
  -d "$BODY"

응답, 시간 초과, 재전송

  • 2xx 상태의 응답은 모두 성공으로 간주됨. 리디렉션(3xx)은 따라가지 않음
  • 서버는 10초 안에 응답해야 하며, 연결 수립에는 최대 5초가 주어짐
  • 하나의 이벤트에 대한 모든 웹훅은 합쳐서 25초를 씀. 그때까지 차례가 오지 않은 웹훅은 실패로 표시되며 재전송할 수 있음
  • LinkHarbor는 실패한 전송을 자동으로 다시 시도하지 않음
  • 실패한 전송은 직접 다시 보낼 수 있지만, 그사이 성공한 전송은 다시 보낼 수 없음. 재전송에는 새 id와 새 created_at이 붙음
  • 이벤트마다 첫 전송을 포함해 최대 3번 시도할 수 있으며, 그 뒤에는 “데드 레터”로 가서 더 이상 다시 보낼 수 없음

대시보드의 전송 내역

“전송 내역”을 누르면 웹훅의 로그가 열립니다. 세 개의 탭이 있습니다.

“전체 전송 내역”
모든 전송이며, 이벤트, 상태(“성공 (2xx)”, “클라이언트 오류 (4xx)”, “서버 오류 (5xx)”, “실패”), “개수 제한” 필터가 있습니다. “↺ 재시도”는 이후에 성공한 전송이 없는 한 실패한 전송을 다시 보냅니다. 행을 클릭하면 요청, 응답, 본문, 원본 JSON이 표시됩니다.
“재실행”
재전송 체인을 “복구됨” 또는 “여전히 실패 중” 결과와 시도 횟수와 함께 보여 줍니다.
“데드 레터”
허용된 모든 시도(첫 전송과 재전송)가 실패한 전송입니다. 이 탭은 보기 전용이며 더 이상 다시 보낼 수 없습니다. 엔드포인트를 확인하세요. 새 이벤트는 그 뒤 평소처럼 전송됩니다. 데이터가 필요하면 API로 가져오세요.

재전송이 되지 않으면 대시보드가 이유를 알려 줍니다.

“다시 전송되지 않았습니다: 이 이벤트는 이미 …번 실패하여 더 이상 시도할 수 없습니다. ‘데드 레터’에서 확인할 수 있습니다.”
3번의 시도를 모두 썼습니다.
“다시 전송되지 않았습니다: 이 이벤트는 그사이 이미 성공적으로 전송되었습니다.”
같은 체인의 이후 전송이 이미 성공했습니다.

대시보드에서 검증하기

“검증”에는 두 가지 도구가 있습니다.

“검증 키트”
“키트 불러오기”를 누르면 알고리즘, 헤더 이름, 서명되는 메시지의 형식, 권장 허용 시간 300초가 표시됩니다. 시크릿 자체는 포함되지 않습니다.
“서명 검증”
받은 전송의 전송 ID, 타임스탬프, 서명, 원본 요청 본문을 입력합니다. 그러면 LinkHarbor가 “서명이 올바릅니다” 또는 “서명이 올바르지 않습니다”를 알려 줍니다. 타임스탬프와 서명은 X-LinkHarbor-Timestamp와 X-LinkHarbor-Signature 헤더에서 복사하세요. 확인은 최근 5분 안의 전송에만 작동하며, 그보다 오래된 전송은 서명이 맞아도 “서명이 올바르지 않습니다”로 표시됩니다.

시크릿 교체, 사용 중지, 삭제

  • “시크릿 ↺”는 새 서명 시크릿을 만들어 한 번 보여 줌. 이전 시크릿은 즉시 작동을 멈추므로 서버가 새 시크릿을 써야 함
  • “편집”에서 상태를 “사용 중지”로 설정하면 전송이 멈춤
  • 휴지통 아이콘은 웹훅을 삭제하며, 메시지를 닫기 전까지 “실행 취소”로 취소할 수 있음

더 작은 플랜으로 바꾸면 새 한도를 넘는 웹훅이, Creator Free에서는 모든 웹훅이 사용 중지됩니다. 웹훅은 유지되며, 업그레이드하면 LinkHarbor가 사용 중지된 웹훅을 새 플랜의 한도까지 자동으로 다시 켭니다. 직접 사용 중지한 웹훅도 포함됩니다.

원하는 내용을 찾지 못했나요?

필요한 내용을 알려 주시면 지원팀이 직접 도와드립니다.

지원팀에 문의