> For the complete documentation index, see [llms.txt](https://genos-docs.gitbook.io/default/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://genos-docs.gitbook.io/default/v1.9.3/admin-management/settings/event-webhook.md).

# 이벤트 웹훅

## 개요

GenOS에서 발생한 알림을 외부 서비스(Slack·Teams·n8n 등)로도 함께 내보냅니다. 관리자는 **관리 > 환경 설정 > 이벤트 웹훅** 탭에서 연동처(수신 URL)를 등록하고, 그 연동처로 내보낼 알림 종류를 체크박스로 고릅니다.

GenOS 알림함에 뜨는 것과 같은 이벤트가 나갑니다 — 알림함에 표시되지 않는 이벤트는 웹훅으로도 가지 않습니다. 설정은 저장 즉시 반영되며 서비스 재시작이 필요 없습니다.

GenOS가 제공하는 범위는 **등록된 URL로 정해진 형식의 데이터를 보내는 것**까지입니다. Slack·Teams·n8n 등에서 받아보는 구성이 가능하지만, **받는 쪽 엔드포인트는 사용처에서 준비·구성해야 합니다.** 자세한 내용은 [수신처 구현](#수신처-구현)을 참고하세요.

<figure><img src="https://541419907-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKhN1XWFV3oOVnFMMIHTE%2Fuploads%2Fgit-blob-ee5e9db2d499e05a8c9ede5466399955aea4582e%2Fwebhook_01_overview.jpg?alt=media" alt="이벤트 웹훅 탭 — 등록된 연동처 목록과 각 연동처의 수신 URL·알림 종류 개수"><figcaption><p>이벤트 웹훅 탭 — 등록된 연동처 목록과 각 연동처의 수신 URL·알림 종류 개수</p></figcaption></figure>

## 주요 개념

| 개념       | 설명                                                                                             |
| -------- | ---------------------------------------------------------------------------------------------- |
| 연동처      | 알림을 받을 외부 수신처 1곳. 이름·수신 URL·발송 사용 여부·내보낼 알림 종류를 갖습니다. 여러 개 등록할 수 있고 연동처마다 다른 알림 종류를 고를 수 있습니다. |
| 수신 URL   | 연동처마다 **하나**입니다. GenOS가 이 주소로 `POST` 요청을 보냅니다. Slack·Teams처럼 URL 자체에 토큰이 포함된 형태를 그대로 씁니다.      |
| 발송 사용    | 끄면 알림 종류 설정을 남긴 채 발송만 멈춥니다. 목록에서 사라지지 않으므로 언제든 다시 켤 수 있습니다.                                    |
| 알림 종류    | 내보낼 알림 타입. 체크된 것만 나갑니다. 아무것도 체크하지 않으면 그 연동처로는 아무것도 가지 않습니다.                                    |
| 연속 실패 횟수 | 수신처가 응답하지 않은 횟수. 성공하면 0으로 돌아갑니다. **재발송(재시도)을 하지 않으므로**, 이 값이 "지금 연동이 깨져 있다"를 알려주는 유일한 신호입니다.   |

## 연동처 등록

**관리 > 환경 설정 > 이벤트 웹훅**에서 **\[새 연동처]** 를 누르면 등록 창이 열립니다.

<figure><img src="https://541419907-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKhN1XWFV3oOVnFMMIHTE%2Fuploads%2Fgit-blob-1cead1c87ceef934d8fa2076e0d102554dfd9af4%2Fwebhook_02_endpoint_modal.jpg?alt=media" alt="새 연동처 등록 창 — 이름·수신 URL·발송 사용 토글과 알림 종류 체크박스"><figcaption><p>새 연동처 등록 창 — 이름·수신 URL·발송 사용 토글과 알림 종류 체크박스</p></figcaption></figure>

| 입력     | 설명                                    |
| ------ | ------------------------------------- |
| 이름     | 목록에서 구분할 표시 이름 (예: `운영팀 Slack`)       |
| 수신 URL | 알림을 받을 주소. `http` 또는 `https`, 최대 512자 |
| 발송 사용  | 켜면 즉시 발송을 시작합니다                       |
| 내보낼 알림 | 아래 [알림 종류 고르기](#알림-종류-고르기) 참조         |

수신 URL은 **연동처마다 하나**입니다. 같은 알림을 여러 곳으로 보내려면 연동처를 여러 개 등록합니다.

### 등록할 수 없는 주소

수신처는 **GenOS 클러스터 바깥**이어야 합니다. 아래 주소는 저장 단계에서 거부되고 사유가 화면에 표시됩니다.

| 주소                                                        | 결과 | 이유                                                                    |
| --------------------------------------------------------- | -- | --------------------------------------------------------------------- |
| `https://hooks.slack.com/services/...`                    | 허용 | 정상 외부 수신처                                                             |
| `http://192.168.10.20:9099/hook` · `172.20.x` · `10.10.x` | 허용 | **사내 서버는 정상 수신처입니다**                                                  |
| 클러스터 파드 대역 IP                                             | 거부 | 클러스터 내부. 대역은 클러스터에서 자동으로 조회합니다                                        |
| `*.svc` · `*.cluster.local` · `*.local`                   | 거부 | 클러스터 내부 DNS                                                           |
| `localhost` · `127.0.0.1` · `::1`                         | 거부 | 루프백                                                                   |
| `0.0.0.0` · `::`                                          | 거부 | **수신 프로그램이 "아무 인터페이스에서나 받겠다"고 선언하는 bind 주소**입니다. 보내는 쪽이 지정할 목적지가 아닙니다 |
| `169.254.x.x`                                             | 거부 | 링크로컬 (클라우드 메타데이터)                                                     |
| 내부 IP로 해석되는 도메인                                           | 거부 | 저장할 때 도메인을 해석해 판정합니다                                                  |
| `ftp://` 등                                                | 거부 | `http` · `https`만 지원                                                  |

> 수신 프로그램은 `0.0.0.0`으로 **bind**하되, GenOS에 등록하는 URL에는 **실제 접근 가능한 IP나 도메인**을 적습니다.

### 알림 종류 고르기

체크박스는 3단으로 묶여 있습니다.

* **전체 선택** — 모든 종류를 한 번에
* **계열별** — `승인·배포`, `시스템`, `AI 드라이브 공유` 등 묶음 단위. 계열 안에서 일부만 켜져 있으면 중간 상태로 표시됩니다
* **개별** — 종류 하나씩

<figure><img src="https://541419907-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKhN1XWFV3oOVnFMMIHTE%2Fuploads%2Fgit-blob-da3dcd00151b7a26aac633f93e23977bc36fdfb0%2Fwebhook_03_event_types_01.jpg?alt=media" alt="전체 선택 — 모든 종류가 켜지고 계열 머리글이 (21/21)로 표시된다"><figcaption><p>전체 선택 — 모든 종류가 켜지고 계열 머리글이 (21/21)로 표시된다</p></figcaption></figure>

계열 안에서 일부만 켜면 계열 체크박스가 중간 상태(`—`)로 바뀌고, 머리글에 켜진 개수가 표시됩니다.

<figure><img src="https://541419907-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKhN1XWFV3oOVnFMMIHTE%2Fuploads%2Fgit-blob-8db932df6004dc2a60e1fa38fb28efaec25b18ab%2Fwebhook_03_event_types_02.jpg?alt=media" alt="일부 선택 — 계열 체크박스가 중간 상태이고 머리글이 (1/21)로 표시된다"><figcaption><p>일부 선택 — 계열 체크박스가 중간 상태이고 머리글이 (1/21)로 표시된다</p></figcaption></figure>

저장하면 **체크된 상태 전체로 교체**됩니다. 체크를 푼 종류는 발송 대상에서 빠집니다.

> 새 알림 종류가 GenOS에 추가되면 이 목록에 자동으로 나타납니다. 기본은 **꺼짐**이므로, 새 종류를 받으려면 명시적으로 체크해야 합니다.

## 연결 테스트

연동처 행의 **\[테스트]** 를 누르면 그 주소로 테스트 이벤트를 한 건 보냅니다. **실제 발송과 똑같은 경로를 쓰므로, 여기서 성공하면 실제 알림도 그 주소로 도달합니다.**

테스트 이벤트는 실제 알림 종류를 쓰지 않습니다(`"type": "NT0000"`, `"test": true`). 수신처가 알림 종류로 분기 처리를 하더라도 실제 장애로 오인되지 않습니다.

도달하면 수신처에 아래와 같이 기록됩니다. 실제 알림도 같은 형식으로 도착합니다.

<figure><img src="https://541419907-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKhN1XWFV3oOVnFMMIHTE%2Fuploads%2Fgit-blob-f163ecf7fc89ac4c90a16502844cf5be0a5fbf89%2Fwebhook_04_test_result_02.jpg?alt=media" alt="수신처가 받은 이벤트 — 운영 버전 변경 신청(NS0001)과 승인(NS0003), AI 드라이브 공유"><figcaption><p>수신처가 받은 이벤트 — 운영 버전 변경 신청(NS0001)과 승인(NS0003), AI 드라이브 공유</p></figcaption></figure>

도달하지 못하면 화면에 실패 사유가 그대로 표시됩니다.

| 결과 메시지                       | 원인                                                              |
| ---------------------------- | --------------------------------------------------------------- |
| 성공 안내                        | 수신처가 정상 응답                                                      |
| `Cannot connect to host ...` | 주소를 찾을 수 없거나 포트가 닫혀 있음                                          |
| `TimeoutError`               | 연결은 되는데 응답이 없음. 중간 방화벽이 연결만 받아주는 경우, 또는 수신처가 `POST`를 처리하지 않는 경우 |
| `Connection reset by peer`   | 수신처가 연결을 끊음                                                     |
| `HTTP 4xx` · `HTTP 5xx`      | 수신처까지 도달했고 그쪽에서 오류 응답                                           |

## 연동처 상태 확인

목록에서 각 연동처의 상태를 확인합니다.

| 표시       | 의미                                         |
| -------- | ------------------------------------------ |
| 알림 종류 개수 | 이 연동처로 내보내도록 체크한 종류 수                      |
| 발송 중지    | 발송 사용이 꺼진 상태. 설정은 그대로 보존됩니다                |
| 연속 실패 N회 | 최근 N번 연속으로 도달하지 못했습니다. 마지막 실패 사유가 함께 표시됩니다 |

## 수신처가 받는 데이터

### 요청 형태

```
POST <등록한 수신 URL>
Content-Type: application/json
X-GenOS-Event: 1
```

`X-GenOS-Event` 헤더는 GenOS가 보낸 요청임을 표시합니다. 수신 URL을 실수로 GenOS 자신으로 지정했을 때 알림이 알림을 낳는 무한 루프를 한 단계에서 끊는 데 쓰입니다.

### 본문 구조

```json
{
  "event": "notification",
  "event_id": "8193548a-4f51-4b37-a5db-d1fa6422dc3e",
  "occurred_at": "2026-08-10T16:28:48.505185",
  "type": "NT0302",
  "type_name": "파드 실행 중단",
  "status": "NS0001",
  "title": "컨테이너 확정 실패",
  "message": "llmops/llmops-code-serving-6-11-xxxx 컨테이너 code-serving 가 ErrImagePull 상태(30초 이상)",
  "resource_type": "k8s_workload",
  "resource_id": 2012587084,
  "resource_revision_id": null
}
```

### 필드 설명

| 필드                     | 타입            | 설명                                                                                                                                                                |
| ---------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event`                | 문자열           | 항상 `"notification"`. 앞으로 다른 종류의 이벤트가 추가될 때를 대비한 구분자입니다                                                                                                            |
| `event_id`             | 문자열(UUID)     | **발송 1건마다 새로 생기는 고유 값.** 같은 사건이 여러 연동처로 갈 때는 **모두 같은 값**이라, 수신처가 같은 사건인지 판별할 수 있습니다                                                                               |
| `occurred_at`          | 문자열           | 발송 시각. ISO 8601 형식이며 **KST 기준, 타임존 표기가 없습니다** (예: `2026-08-10T16:28:48.505185`)                                                                                   |
| `type`                 | 문자열           | 알림 종류 코드. **어떤 종류의 알림인지**를 나타내며, 그 알림이 지금 어떤 단계인지는 `status` 가 나타냅니다. 결재는 신청부터 처리 결과까지 같은 `type` 을 유지하고 `status` 만 바뀝니다 — [결재 알림은 두 갈래로 옵니다](#결재-알림은-두-갈래로-옵니다) 참조 |
| `type_name`            | 문자열 \| `null` | 알림 종류의 한글 이름. **이름이 정의되지 않은 종류는 `null`** 입니다. 분기 처리는 `type`으로 하세요                                                                                                 |
| `status`               | 문자열           | 알림 상태. `NS0001` 발생 / `NS0002` 취소 / `NS0003` 완료·해제                                                                                                                 |
| `title`                | 문자열           | 알림 제목. 최대 64자                                                                                                                                                     |
| `message`              | 문자열           | 알림 본문. 사람이 읽는 문장이며 종류마다 형식이 다릅니다                                                                                                                                  |
| `resource_type`        | 문자열 \| `null` | 알림이 가리키는 대상 종류. 대상이 없는 알림은 `null`                                                                                                                                 |
| `resource_id`          | 정수 \| `null`  | 대상 식별자                                                                                                                                                            |
| `resource_revision_id` | 정수 \| `null`  | 대상의 리비전 식별자. 리비전 개념이 없는 알림은 `null`                                                                                                                                |
| `test`                 | 불린            | **\[테스트] 버튼으로 보낸 경우에만** `true`로 포함됩니다. 실제 알림에는 이 필드가 없습니다                                                                                                         |

> 수신자(누구에게 간 알림인지) 정보는 담기지 않습니다. 웹훅은 **사건 단위**로 나가며 GenOS 내부 수신자와 무관합니다.

### `status` 값

| 값        | 의미    | 예               |
| -------- | ----- | --------------- |
| `NS0001` | 발생    | 장애 감지, 결재 신청 접수 |
| `NS0002` | 취소    | 결재 신청 취소        |
| `NS0003` | 완료·해제 | 장애 회복, 결재 처리 완료 |

같은 사건의 발생(`NS0001`)과 해제(`NS0003`)는 **`type` · `resource_type` · `resource_id`가 같습니다.** 수신처에서 짝을 맞추려면 이 세 값을 키로 쓰세요.

> 🔴 **`type` 과 `status` 는 짝으로 읽습니다.** `type` 이 알림의 종류를, `status` 가 그 알림의 현재 단계를 나타냅니다. 결재는 신청·승인·취소가 **같은 `type` 안에서 `status` 로 구분**되므로, `type` 만 보고 처리 결과를 판단하면 어긋납니다 — [결재 알림은 두 갈래로 옵니다](#결재-알림은-두-갈래로-옵니다) 참조.

### `resource_type` 값

알림이 가리키는 리소스의 종류입니다. 자주 보이는 값은 아래와 같습니다.

| 값                                                      | 대상             | 함께 오는 식별자                                               |
| ------------------------------------------------------ | -------------- | ------------------------------------------------------- |
| `approval`                                             | 결재 건           | `resource_id` = 결재 ID                                   |
| `serving`                                              | 모델 서빙          | `resource_id` = 서빙 ID, `resource_revision_id` = 리비전 ID  |
| `service_router`                                       | 서비스 라우터        | `resource_id` = 라우터 ID, `resource_revision_id` = 리비전 ID |
| `code_serving` · `codespace`                           | 코드 서빙 · 코드스페이스 | `resource_id` = 리소스 ID                                  |
| `ai_drive_file` · `ai_drive_folder` · `ai_drive_drive` | AI 드라이브 리소스    | `resource_id` = 리소스 ID                                  |
| `k8s_workload`                                         | 클러스터 워크로드      | `resource_id` = 경보 식별자 (같은 경보의 발생·해제가 동일 값)             |
| `notice`                                               | 공지사항           | `resource_id` = 공지 ID                                   |
| `webhook_test`                                         | 연결 테스트         | 식별자 없음                                                  |
| `null`                                                 | 대상 없음          | 보안등급 변경 등                                               |

> **전체 목록이 아닙니다.** GenOS 에 새 리소스 종류가 추가되면 값도 늘어납니다. 수신처에서 이 값으로 분기할 때는 **모르는 값이 와도 넘어가도록** 기본 처리를 두세요.

## 알림 종류 코드

`type_name`이 비어 있는 항목은 아직 한글 이름이 정의되지 않은 종류입니다. 이 경우에도 알림은 정상 발송되며 `type` 코드는 그대로 옵니다.

> ⚠️ **승인·배포 계열을 고를 때는 한 가지만 확인하세요.** `NT0106`\~`NT0108`(승인·거부·취소)은 **신청자에게 보내는 결과 통보**라, 신청자 본인이 처리한 건은 통보할 대상이 없어 발송되지 않습니다. 처리 결과를 빠짐없이 받으려면 신청 타입(`NT0101` 등)도 함께 체크하고 `status` 로 판별합니다 — [결재 알림은 두 갈래로 옵니다](#결재-알림은-두-갈래로-옵니다) 참조.

| 계열         | 코드       | 이름                 |
| ---------- | -------- | ------------------ |
| 승인·배포      | `NT0101` | 배포 신청              |
|            | `NT0102` | 중지 신청              |
|            | `NT0103` | 재배포 신청             |
|            | `NT0104` | 공유 변경 신청           |
|            | `NT0105` | 이용 신청              |
|            | `NT0106` | 배포 승인              |
|            | `NT0107` | 배포 거부              |
|            | `NT0108` | 배포 취소              |
|            | `NT0109` | 배포 대기              |
|            | `NT0110` | 인증 키 생성 신청         |
|            | `NT0111` | 이관 신청              |
|            | `NT0112` | 이관 승인              |
|            | `NT0113` | 이관 반려              |
|            | `NT0114` | 이관 취소              |
|            | `NT0115` | 이관 완료              |
|            | `NT0116` | 이관 실패              |
|            | `NT0117` | 재이관 시작             |
|            | `NT0118` | 재이관 완료             |
|            | `NT0119` | 재이관 실패             |
|            | `NT0120` | 이관/재이관 부분 실패       |
|            | `NT0122` | 운영 버전 변경 신청        |
| 공지         | `NT0201` | 공지사항               |
| 시스템        | `NT0301` | 컨테이너 에러            |
|            | `NT0302` | 파드 실행 중단           |
|            | `NT0303` | 보안 탐지              |
| 트래픽        | `NT0401` | 트래픽 분배 제외          |
| 서비스 라우터    | `NT0501` | 서비스 라우터 격리         |
| AI 드라이브 공유 | `NT0601` | 공유                 |
|            | `NT0602` | 재공유                |
|            | `NT0603` | 삭제                 |
|            | `NT0604` | 공유 회수              |
|            | `NT0605` | 권한 변경              |
| 보안 등급      | `NT0701` | *(이름 미정의)* 보안등급 부여 |
|            | `NT0702` | *(이름 미정의)* 보안등급 회수 |
|            | `NT0703` | *(이름 미정의)* 보안등급 변경 |

## 결재 알림은 두 갈래로 옵니다

결재(배포 신청·승인·거부·취소, 이관 등)는 받는 사람이 둘이라 **두 개의 알림 갈래**로 나갑니다. 승인자에게 가는 갈래와 신청자에게 가는 갈래가 각각 다른 `type` 을 씁니다. 알림 종류를 고를 때 이 구조를 알아야 원하는 이벤트를 빠짐없이 받을 수 있습니다.

|          | 갈래 A — 신청 알림                           | 갈래 B — 결과 통보                                       |
| -------- | -------------------------------------- | -------------------------------------------------- |
| 원래 받는 사람 | 승인자                                    | 신청자                                                |
| `type`   | **신청 타입 그대로** (`NT0101` 등)             | 결과 전용 타입 (`NT0106` 승인 / `NT0107` 거부 / `NT0108` 취소) |
| 결과 표현    | `status` 로 (`NS0002` 취소 · `NS0003` 완료) | 항상 `NS0001`                                        |
| 발송 조건    | **항상**                                 | **처리자 ≠ 신청자일 때만**                                  |

갈래 A 가 결과 시점에도 신청 타입을 유지하는 이유는, 승인자의 알림함에 이미 떠 있는 "신청" 알림을 찾아 종결 처리해야 하기 때문입니다. 같은 갈래를 이어가야 짝이 맞습니다.

### 실제로 오는 조합

배포 신청(`NT0101`)을 예로 든 것이며, 다른 결재 종류도 구조는 같습니다.

| 실제 사건      | 갈래 A                | 갈래 B                |
| ---------- | ------------------- | ------------------- |
| 신청         | `NT0101` / `NS0001` | —                   |
| 타인이 승인     | `NT0101` / `NS0003` | `NT0106` / `NS0001` |
| **본인이 승인** | `NT0101` / `NS0003` | **없음**              |
| 타인이 거부     | `NT0101` / `NS0003` | `NT0107` / `NS0001` |
| 타인이 취소     | `NT0101` / `NS0002` | `NT0108` / `NS0001` |
| **본인이 취소** | `NT0101` / `NS0002` | **없음**              |

### 자주 묻는 두 가지

**1. 취소된 건인데 `type_name` 이 "배포 신청"으로 보입니다.**

갈래 A 는 신청부터 처리까지 하나의 알림을 이어가는 갈래입니다. 그래서 취소 시점에도 `type` 은 `NT0101` 그대로이고 `status` 가 `NS0002`(취소)로 바뀝니다. `type_name` 도 "배포 신청" 그대로 옵니다 — 취소되었다는 사실은 `status` 와 `message` 에 담깁니다.

```json
{
  "type": "NT0101", "type_name": "배포 신청", "status": "NS0002",
  "message": "'김재은'님이 '[전용] 채팅' 배포 신청 취소",
  "resource_type": "approval", "resource_id": 33572
}
```

**처리 결과는 `status` 로 판단합니다.** `NS0001` 이면 새 신청, `NS0002` 면 취소, `NS0003` 이면 처리 완료입니다.

**2. "배포 취소"만 체크하면 일부 취소를 놓칩니다.**

`NT0108`(배포 취소)은 신청자에게 보내는 결과 통보라, 신청자 본인이 취소하면 통보할 대상이 없어 발송되지 않습니다. 취소를 빠짐없이 받으려면 신청 타입(`NT0101` 등)도 함께 체크하고 `status == NS0002` 로 판별합니다.

| 목적             | 체크할 종류                                   | 판별                                      |
| -------------- | ---------------------------------------- | --------------------------------------- |
| 결재 처리 결과를 빠짐없이 | 신청 타입 (`NT0101` · `NT0102` · `NT0103` …) | `status` 가 `NS0002`(취소) 또는 `NS0003`(완료) |
| 신청자에게 간 결과 통보만 | `NT0106` · `NT0107` · `NT0108`           | — (본인 처리 건은 오지 않음)                      |
| 둘 다            | 위 전부                                     | 같은 `resource_id` 로 두 건이 오므로 수신처에서 접기    |

## API 호출 방식

연동처 관리는 관리자 인증 토큰으로 호출합니다. 모든 경로는 관리자 권한이 필요합니다.

| 메서드      | 경로                                  | 용도                         |
| -------- | ----------------------------------- | -------------------------- |
| `GET`    | `/admin/webhook/event-types`        | 선택 가능한 알림 종류 목록 (코드·이름·계열) |
| `GET`    | `/admin/webhook`                    | 연동처 목록 (알림 종류·발송 상태 포함)    |
| `POST`   | `/admin/webhook`                    | 연동처 등록                     |
| `PUT`    | `/admin/webhook/{endpoint_id}`      | 연동처 수정                     |
| `POST`   | `/admin/webhook/{endpoint_id}/test` | 테스트 이벤트 발송                 |
| `DELETE` | `/admin/webhook/{endpoint_id}`      | 연동처 삭제                     |

```bash
curl -X POST "https://{ADMIN_API_HOST}/admin/webhook" \
  -H "Authorization: Bearer {ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "운영팀 Slack",
    "url": "https://hooks.slack.com/services/T000/B000/xxxx",
    "is_enabled": true,
    "notification_types": ["NT0101", "NT0302"]
  }'
```

`notification_types`는 **부분 갱신이 아니라 전체 교체**입니다. 수정할 때 요청에 없는 종류는 꺼집니다.

테스트 발송은 응답 본문으로 결과를 돌려줍니다. **도달 실패는 HTTP 오류가 아니라 `success: false`로 옵니다** — 요청 자체는 정상 처리됐고 수신처가 받지 못한 것이기 때문입니다.

```json
{ "success": false, "error": "TimeoutError" }
```

## 오류·예외 처리

수신 URL 검증에 걸리면 저장이 거부되고 화면에 사유가 표시됩니다.

| 발생 상황                   | 표시 문구                                                  |
| ----------------------- | ------------------------------------------------------ |
| `http`·`https`가 아닌 스킴   | 웹훅 URL 은 http 또는 https 여야 합니다.                         |
| 호스트 없음                  | 웹훅 URL 에 호스트가 없습니다.                                    |
| 도메인 해석 실패               | 웹훅 URL 의 호스트({호스트})를 해석할 수 없습니다.                       |
| 클러스터 내부 DNS·`localhost` | 클러스터 내부 주소({호스트})는 웹훅 수신처로 등록할 수 없습니다.                 |
| 클러스터 파드 대역              | 클러스터 내부 대역({대역})으로 해석되는 주소({호스트})는 웹훅 수신처로 등록할 수 없습니다. |
| `0.0.0.0` · `::`        | 0.0.0.0 같은 미지정 주소는 수신처가 될 수 없습니다. 실제 IP나 도메인을 적어주세요.   |
| 루프백                     | 루프백 주소는 웹훅 수신처로 등록할 수 없습니다.                            |
| 링크로컬                    | 링크로컬 주소는 웹훅 수신처로 등록할 수 없습니다.                           |
| 512자 초과                 | 웹훅 URL 이 너무 깁니다(최대 512자).                              |

## 수신처 구현

> 🔴 **GenOS가 하는 일은 "등록된 URL로 위 형식의 JSON을 POST하는 것"까지입니다.** 받은 데이터를 어떻게 해석하고 어디에 표시할지는 **수신처에서 구성해야 합니다.**

Slack·Teams·n8n 같은 도구로 알림을 받아보는 구성은 가능하지만, **URL만 등록한다고 바로 보이지는 않습니다.** 각 도구가 기대하는 메시지 형식이 GenOS 페이로드와 다르기 때문입니다.

| 수신 방식                          | 필요한 작업                                                                                                              |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| 자체 수신 서버                       | GenOS 페이로드를 받아 원하는 형태로 처리 (아래 예시)                                                                                   |
| n8n · Zapier 등 자동화 도구          | Webhook 노드로 수신 → 필드를 매핑해 Slack·메일 등으로 전달                                                                            |
| Slack · Teams Incoming Webhook | **중계가 필요합니다.** 이들은 각자의 메시지 스키마(예: Slack `{"text": ...}`)를 요구하므로, GenOS 페이로드를 그 형식으로 변환해 전달하는 수신 서버나 자동화 도구를 사이에 둡니다 |

GenOS는 **HTTP 상태 코드만** 봅니다.

* `2xx` · `3xx` → 성공으로 기록
* `4xx` · `5xx` → 실패로 기록하고 연속 실패 횟수 증가

응답 본문은 해석하지 않습니다. 본문에 오류를 담고 `200`을 반환하는 방식이면 GenOS는 성공으로 봅니다.

아래는 받은 내용을 그대로 출력하는 최소 수신 서버입니다. 실제 연동에서는 이 자리에서 원하는 도구의 형식으로 변환해 전달합니다.

```python
import http.server, socketserver, json

class Handler(http.server.BaseHTTPRequestHandler):
    def do_POST(self):
        body = json.loads(self.rfile.read(int(self.headers.get('Content-Length', 0))))

        if body.get('test'):
            print('연결 테스트 수신')
        else:
            print(f"[{body['type']}] {body['title']} — {body['message']}")

        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'ok')

    def log_message(self, *args):
        pass

socketserver.TCPServer.allow_reuse_address = True
socketserver.TCPServer(("0.0.0.0", 9099), Handler).serve_forever()
```

## 알아둘 동작

**재발송하지 않습니다.** 수신처가 응답하지 않으면 그 이벤트는 유실되며 나중에 다시 보내지 않습니다. 수신처를 껐다 켜는 동안 발생한 알림은 오지 않습니다. 유실 여부는 목록의 연속 실패 횟수로만 확인할 수 있습니다. 연동처를 잠시 점검할 때는 삭제하지 말고 **발송 사용을 끄면** 설정이 보존됩니다.

**설정 변경은 최대 10초 뒤에 반영됩니다.** 발송 측이 설정을 짧게 캐시하므로, 알림 종류 체크를 해제한 직후 한 건이 더 도착할 수 있습니다. 정상 동작입니다.

**한 번의 작업에서 여러 건이 올 수 있습니다.** 웹훅은 받는 사람 수와 무관하지만, GenOS 내부에서 알림을 여러 번 발송하는 경우가 있습니다.

| 상황              | 도착 건수                                                                            |
| --------------- | -------------------------------------------------------------------------------- |
| 대부분의 알림         | 1건                                                                               |
| 결재 처리(승인·거부·취소) | 최대 **2건** — 갈래가 둘입니다. 처리자가 신청자 본인이면 1건. [결재 알림은 두 갈래로 옵니다](#결재-알림은-두-갈래로-옵니다) 참조 |
| 보안등급 변경         | **대상 사용자 수만큼** — 사용자마다 문구가 다른 개인 알림입니다                                           |

**같은 경보가 하루 뒤 다시 올 수 있습니다.** 클러스터 장애 경보(`NT0301` · `NT0302` · `NT0303`)는 조건이 계속 유지되면 24시간마다 다시 발송되며, 이때 `event_id`는 새 값입니다. 같은 사건인지 판별하려면 `type` + `resource_type` + `resource_id` + `status` 조합을 쓰세요.

**수신 URL은 관리자에게 그대로 보입니다.** 목록·수정 화면에서 URL 전문이 표시되므로, URL에 토큰이 포함된 경우 GenOS 관리자 권한자는 그 토큰을 볼 수 있습니다.

**접근 권한**은 관리자에게만 열립니다. 별도의 권한 부여 작업이나 설정 파일 변경은 필요 없습니다.

## 사용자 시나리오

GenOS가 제공하는 것은 **등록한 URL로 알림을 내보내는 것**까지입니다. 아래 시나리오의 수신처는 모두 **사용처에서 준비·구성한 것**을 전제로 합니다.

**배포 승인 알림을 외부에서 함께 보기**

수신처를 먼저 준비합니다 — GenOS 페이로드를 받을 수 있는 자체 서버나, Webhook 수신을 지원하는 자동화 도구의 엔드포인트입니다. 그 주소를 **\[새 연동처]** 에 등록하고 알림 종류에서 `승인·배포` 계열을 일괄 선택해 저장한 뒤, **\[테스트]** 로 도달을 확인합니다. 이후 배포 신청·승인·거부가 발생할 때마다 그 주소로 전달됩니다. 협업 도구 화면에 어떤 형태로 표시할지는 수신처에서 결정합니다.

**장애 알림만 다른 수신처로 분리**

연동처를 하나 더 등록하고 알림 종류에서 `시스템` 계열(`NT0301` · `NT0302` · `NT0303`)만 체크합니다. 두 연동처는 서로 독립적이라 한쪽이 응답하지 않아도 다른 쪽은 정상 도착합니다. 수신처를 다르게 두면 장애 알림과 승인 알림을 서로 다른 담당자에게 보낼 수 있습니다.

**수신처 점검 중 일시 중단**

수신 서버를 점검할 때는 연동처를 삭제하지 말고 **발송 사용**을 끕니다. 알림 종류 설정이 보존되고, 점검이 끝나면 다시 켜기만 하면 됩니다. 다만 **꺼져 있는 동안 발생한 알림은 재발송되지 않습니다.**

**연동이 끊긴 것을 발견**

목록에 `연속 실패 N회`와 마지막 실패 사유가 표시됩니다. **\[테스트]** 로 현재 상태를 즉시 확인하고, 사유에 따라 주소·방화벽·수신 프로그램을 점검합니다. 복구되면 다음 발송 성공 시 실패 횟수가 0으로 돌아갑니다.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://genos-docs.gitbook.io/default/v1.9.3/admin-management/settings/event-webhook.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
