웹훅

Audience 구조

Lenit 웹훅의 핵심 가치. idea.status_changedannouncement.published 이벤트에 한해, 누구에게 알림을 보내야 할지 자사 서버가 바로 알 수 있도록 그 아이디어와 관련된 사용자 정보를 동봉합니다.

4가지 audience 타입

타입설명기준
author그 아이디어를 올린 사람단일 객체 또는 없음
commenters그 아이디어에 공개 댓글을 단 모든 사람배열, 비공개 관리자 메모 작성자는 제외
voters그 아이디어에 투표한 식별된 엔드유저배열, 비식별 방문자(핑거프린트) 제외
reactors그 아이디어와 연결된 공지에 이모지 리액션을 단 식별된 엔드유저배열, 핑거프린트 제외

audience 객체 구조

json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "audience": {
    "author": {
      "externalId": "user_123",
      "traits": { "plan": "pro", "userType": "heavy", "signupDays": 120 }
    },
    "commenters": [
      { "externalId": "user_456", "traits": { "plan": "free" } },
      { "externalId": "user_789", "traits": { "plan": "pro" } }
    ],
    "voters":   [ /* 동일 구조 */ ],
    "reactors": [ /* 동일 구조 */ ]
  }
}
  • externalId — 위젯 초기화 시 자사가 넘긴 그 userId
  • traits — 위젯 초기화 시 자사가 넘긴 그 객체

audience targets — 받는 사람 커스터마이즈

웹훅 등록 시 어떤 그룹을 받을지 체크박스로 선택합니다. 선택하지 않은 그룹은 페이로드에 아예 포함되지 않습니다.

엔드포인트별로 다른 audience
text
1
2
3
4
5
6
7
8
[웹훅 A]  author + voters
         → 작성자와 투표자에게만

[웹훅 B]  commenters
         → 댓글 단 사람에게만

[웹훅 C]  author + commenters + voters + reactors
         → 모두에게

엔드포인트별로 다르게 설정 가능합니다. 같은 이벤트라도 웹훅 A와 B가 받는 audience가 다를 수 있습니다.

제외되는 사용자 (의도적)

주의audience에서 항상 빠지는 케이스
  • 비식별 방문자(핑거프린트)의 투표/리액션 — 식별 정보(externalId)가 없으니 알림 발송이 불가능. 페이로드에서 제외.
  • 비공개 관리자 댓글 작성자is_private = true인 댓글은 관리자 내부 메모. 일반 댓글자로 취급하지 않음.
  • 관리자가 직접 게시한 아이디어의 authoridea.author가 EndUser가 아닌 경우 audience.author는 비어있음.

대량 발송 방지 — 500명 상한

각 audience 그룹은 최대 500명까지 포함됩니다. 이를 넘으면 자르고 발송. 이는:

  • 웹훅 페이로드 비대화 방지
  • 자사 서버가 처리 시간 초과로 504 반환하는 것을 방지

500명 이상에게 발송할 일이 있다면 이벤트 큐로 받아서 자사 사이드에서 페이지네이션해 처리하는 패턴을 권장합니다 (레시피 6 — 재시도 큐).

자사 서버에서 활용하기 — 핵심 패턴

audience 합치기 + dedupe
js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const event = JSON.parse(req.body);

if (event.event === "idea.status_changed" && event.data.audience) {
  const a = event.data.audience;
  const recipients = [
    a.author,
    ...(a.commenters || []),
    ...(a.voters     || []),
    ...(a.reactors   || []),
  ].filter(Boolean);

  // 같은 사람이 여러 그룹에 속할 수 있음 → externalId 로 dedupe
  const seen = new Set();
  const unique = recipients.filter(u => {
    if (seen.has(u.externalId)) return false;
    seen.add(u.externalId);
    return true;
  });

  for (const user of unique) {
    // externalId 로 자사 DB 에서 email/phone 조회 → 자사 채널 발송
    await notifyUser(user.externalId, {
      title: event.data.title,
      newStatus: event.data.newStatus,
    });
  }
}

traits 활용 — 세그먼트 라우팅

traits로 어떤 사용자에게 어떻게 알릴지 분기할 수 있습니다.

유료/무료 사용자에게 다른 채널 사용
js
1
2
3
4
5
6
7
for (const user of unique) {
  const channel = user.traits?.plan === "pro"
    ? "kakao-alimtalk"     // 유료 사용자에게는 카카오 알림톡
    : "email";             // 무료 사용자에게는 이메일

  await sendByChannel(channel, user.externalId, ...);
}

다음 단계

이벤트 종류수신 코드 레시피