인시던트 웹훅 푸시¶
Webhook은 인시던트의 주요 변경 사항을 타사 시스템으로 푸시하는 데 사용됩니다. 인시던트 정보를 ITSM, 자동화 플랫폼 또는 내부 업무 시스템에 연동하여 타사가 수신된 데이터를 기반으로 티켓을 생성하거나, 상태를 동기화하거나, 자동화 워크플로우를 트리거할 수 있습니다.
인시던트 센터 > 설정 관리 > Webhook에서 Webhook을 생성하고 관리할 수 있습니다.
전제 조건¶
먼저 POST 요청을 수신할 수 있는 타사 주소를 준비하세요. Webhook은 HTTP 및 HTTPS 주소를 지원합니다. 프로덕션 환경에서는 HTTPS를 사용하는 것이 좋습니다. 보안상의 이유로 내부 네트워크, 루프백 또는 로컬 호스트 주소는 구성할 수 없습니다.
Webhook 구성¶
- Webhook 페이지에서 웹훅 생성을 클릭합니다.
- 이름과 타사 수신 URL을 입력합니다.
- 자동으로 푸시할 인시던트 이벤트를 선택합니다. | 이벤트 | 설명 | | --- | --- | | 인시던트 생성 | 새 인시던트가 생성될 때 푸시합니다. | | 상태 변경 | 인시던트 상태가 변경될 때 푸시합니다. | | 등급 변경 | 인시던트 등급이 변경될 때 푸시합니다. | | 담당자 변경 | 인시던트 담당자가 변경될 때 푸시합니다. | | 집계 변경 | 집계 인시던트의 연결된 이벤트가 변경될 때 푸시합니다. 인시던트 집계 규칙을 통해 생성된 인시던트에만 적용됩니다. |
- 토큰 생성을 클릭합니다. 토큰은 Guance 플랫폼에서만 생성됩니다. 즉시 복사하여 타사 수신 서비스의 키 구성에 안전하게 저장하세요.
- 활성화 상태를 설정하고 저장합니다.
자동 트리거 이벤트는 선택하지 않아도 됩니다. Webhook이 활성화되면 자동 트리거 이벤트가 구성되지 않은 경우에도 인시던트 상세 페이지에서 수동 전송을 사용할 수 있습니다.
참고
- 토큰은 Webhook의 인증 키입니다. 생성 후 플랫폼은 한 번만 평문을 표시하며, 이후 페이지에서는 마스킹된 값만 표시됩니다. 토큰을 다시 생성하면 이전 토큰이 즉시 만료됩니다.
- Webhook 전송 실패는 인시던트의 생성, 업데이트, 복구 및 종료에 영향을 미치지 않습니다.
- Webhook은 고정된 요청 방식, 인증 방식 및 시간 제한을 사용하므로 추가 구성이 필요하지 않습니다.
테스트 전송¶
Webhook을 저장한 후 해당 Webhook의 작업 항목에서 테스트를 클릭하고 이벤트 유형을 선택할 수 있습니다. 시스템은 정식 요청 형식으로 테스트 데이터를 전송하며, Payload의 isTest는 true입니다.
테스트 결과는 전송 내역에서 확인할 수 있습니다. 테스트 전송은 실제 인시던트에 영향을 미치지 않으며 정식 전송 통계에 포함되지 않습니다.
수동 전송¶
인시던트 상세 페이지에서 Webhook 전송을 클릭하면 인시던트의 최신 스냅샷을 활성화된 Webhook으로 즉시 전송할 수 있습니다. Webhook을 선택하고 1~1000자의 전송 설명을 입력한 후 전송할 수 있습니다.
수동 전송의 특징은 다음과 같습니다.
- 모든 인시던트 상태를 지원하며, 해당 Webhook의 자동 트리거 이벤트 구성 및 집계 창의 영향을 받지 않습니다.
- 한 번에 하나의 Webhook만 선택할 수 있습니다. 동일한 구성원이 5초 이내에 동일한 인시던트 및 동일한 Webhook에 대해 중복 제출할 수 없습니다.
2xx상태 코드를 반환하면 성공으로 간주합니다. 네트워크 오류, URL 무효, 응답 시간 초과 또는2xx가 아닌 상태 코드는 실패로 간주합니다.- 전송 결과는 전송 내역에 기록되며, 이벤트 유형은 "수동 트리거"로 표시되고 전송 유형은 "정식"으로 유지됩니다.
- 전송 실패 시 자동 재시도되지 않습니다. 다시 전송하려면 인시던트 상세 페이지로 돌아가서 다시 트리거하세요.
수동 전송의 Payload는 요청 프로토콜 및 인시던트 스냅샷 구조를 따르며, 다음 정보가 추가됩니다.
| 필드 | 설명 |
|---|---|
eventTypes |
incident.manual_triggered를 포함합니다. |
triggerSource |
manual로 고정됩니다. |
manualContext |
수동 전송 컨텍스트로, 작업자, 전송 설명 및 작업 시간을 포함합니다. |
수동 전송은 인시던트 협업 기록 또는 활동 타임라인에 새 레코드를 추가하지 않습니다.
요청 프로토콜¶
시스템은 POST 방식으로 JSON 데이터를 전송하며, 요청 시간 제한은 5초이고 리디렉션을 따르지 않습니다. 수신 서비스가 2xx 상태 코드를 반환하면 전송 성공으로 간주합니다.
요청 헤더¶
| Header | 설명 |
|---|---|
Content-Type |
application/json으로 고정됩니다. |
Authorization |
Bearer <Token> 형식으로 고정됩니다. |
X-Incidents-Event-Id |
이 전송의 고유 식별자로, 수신 측에서 멱등성 처리에 사용할 수 있습니다. |
X-Incidents-Timestamp |
Unix 초 단위 타임스탬프입니다. |
X-Incidents-Signature |
HMAC-SHA256 서명으로, v1=<signature> 형식입니다. |
Payload 예시¶
{
"eventId": "evt_01JXYZ...",
"eventTypes": [
"incident.created",
"incident.status_changed"
],
"eventTime": 1783000000,
"workspaceUUID": "wksp_xxx",
"incident": {
"uuid": "incident_xxx",
"name": "API service unavailable",
"level": "level_1",
"incidentsStatus": "working",
"resourceType": "incident_aggregation",
"aggregationRuleUUID": "rule_xxx",
"aggregationRuleNameSnapshot": "生产环境服务聚合",
"eventRelations": [
{
"df_fault_id": "fault_xxx",
"status": "critical",
"aggregationValues": {
"df_workspace_name": "production",
"df_dimension_tags.host": "web-01"
}
}
]
},
"isTest": false
}
필드 설명:
| 필드 | 설명 |
|---|---|
eventId |
Webhook 전송 배치의 고유 ID로, 원본 이벤트 ID가 아닙니다. |
eventTypes |
이 배치에 포함된 인시던트 변경 유형입니다. |
eventTime |
이 전송의 Unix 초 단위 타임스탬프입니다. |
workspaceUUID |
인시던트가 속한 워크스페이스 UUID입니다. |
incident |
인시던트의 현재 전체 스냅샷입니다. 자세한 필드는 아래를 참조하세요. resourceContent는 푸시되지 않습니다. |
isTest |
테스트 전송 여부입니다. |
incident 필드 설명¶
incident는 전송 시점의 인시던트 스냅샷입니다. 인시던트의 등급, 상태, 담당자 및 연결된 이벤트 변경에 따라 업데이트됩니다.
| 필드 | 타입 | 설명 |
|---|---|---|
id |
integer | 인시던트 레코드의 내부 숫자 ID입니다. |
uuid |
string | 인시던트 UUID로, 형식은 incident_xxx이며 타사 인시던트 기본 키로 사용하는 것이 좋습니다. |
workspaceUUID |
string | 인시던트가 속한 워크스페이스 UUID입니다. |
name |
string | 인시던트 제목입니다. |
level |
string | 현재 인시던트 등급 UUID 또는 등급 식별자입니다. |
description |
string | 인시던트 설명입니다. |
incidentsStatus |
string | 인시던트 비즈니스 상태입니다. 일반적인 값에는 open(미할당), working(처리 중), resolved(해결됨), closed(종료됨)가 있습니다. |
assigner |
array | 현재 담당자 계정 UUID 목록입니다. 미할당 시 빈 배열입니다. |
statusTime |
object | 각 인시던트 비즈니스 상태에 해당하는 타임스탬프 기록입니다. |
statusChangeTime |
integer | 가장 최근 인시던트 비즈니스 상태 변경의 Unix 초 단위 타임스탬프입니다. |
cumulativeTime |
integer | 인시던트 누적 지속 시간(초)입니다. |
eventCount |
integer | 현재 인시던트에 누적 연결된 이벤트 수입니다. |
eventUpdateAt |
integer | 가장 최근 연결된 이벤트 업데이트의 Unix 초 단위 타임스탬프입니다. |
eventRelations |
array | 현재 연결된 이벤트 스냅샷입니다. 자세한 필드 설명은 아래를 참조하세요. |
source |
string | 인시던트 소스 식별자입니다. |
resourceCategory |
string | 소스 리소스 클래스입니다. |
resourceType |
string | 소스 리소스 유형입니다. 집계 규칙으로 생성된 인시던트는 incident_aggregation으로 고정됩니다. |
resourceUUID |
string | 소스 리소스 UUID입니다. |
resourceUrl |
string | 소스 리소스의 액세스 주소입니다. 주소가 없으면 비어 있습니다. |
resourceIdentity |
string | 동일한 소스 인시던트를 식별하는 데 사용되는 리소스 식별자입니다. |
aggregationRuleUUID |
string | 집계 인시던트를 생성한 규칙의 UUID입니다. 집계 인시던트만 반환됩니다. |
aggregationRuleNameSnapshot |
string | 인시던트 생성 시 기록된 집계 규칙의 이름 스냅샷입니다. 집계 인시던트만 반환됩니다. 규칙 이름이 변경되거나 삭제된 후 기록된 소스를 식별하는 데 사용할 수 있습니다. |
dimensionTag |
object | 인시던트와 연결된 차원 태그입니다. |
dtHost |
string | 추출된 호스트 차원 값입니다. |
dtService |
string | 추출된 서비스 차원 값입니다. |
dtResource |
string | 추출된 리소스 차원 값입니다. |
dtPodName |
string | 추출된 Pod 이름 차원 값입니다. |
dtAppName |
string | 추출된 애플리케이션 이름 차원 값입니다. |
dtAppId |
string | 추출된 애플리케이션 ID 차원 값입니다. |
dtEnv |
string | 추출된 환경 차원 값입니다. |
dtUrl |
string | 추출된 URL 차원 값입니다. |
extend |
object | 인시던트 확장 정보입니다. 이 객체는 제품 기능 발전에 따라 확장될 수 있으므로 타사는 인식되지 않는 필드를 무시해야 합니다. |
status |
integer | 인시던트 레코드 상태로, 레코드가 유효한지 식별하는 데 사용됩니다. 인시던트 비즈니스 상태로 사용하지 마세요. |
creator |
string | 인시던트 레코드를 생성한 계정 UUID 또는 시스템 식별자입니다. |
updator |
string | 인시던트 레코드를 가장 최근에 업데이트한 계정 UUID 또는 시스템 식별자입니다. |
createAt |
integer | 인시던트 레코드 생성 시간, Unix 초 단위 타임스탬프입니다. |
updateAt |
integer | 인시던트 레코드의 가장 최근 업데이트 시간, Unix 초 단위 타임스탬프입니다. |
deleteAt |
integer | 인시던트 레코드 삭제 시간입니다. 삭제되지 않은 경우 일반적으로 음수 값입니다. |
eventRelations의 각 항목은 현재 연결된 이벤트를 나타냅니다. 안정적으로 반환되는 필드는 다음과 같습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
df_fault_id |
string | 연결된 이벤트의 인시던트 ID입니다. |
status |
string | 연결된 이벤트의 현재 상태(예: critical 또는 ok)입니다. |
aggregationValues |
object | 집계 키 값 스냅샷입니다. 키는 규칙에 저장된 전체 필드 경로이고, 값은 집계 ID를 생성할 때 사용된 정규화된 값입니다. 집계 인시던트의 연결된 이벤트에 대해서만 반환됩니다. |
다른 이벤트 소스는 추가 필드를 포함할 수 있습니다. 타사는 df_fault_id로 연결된 이벤트를 식별하고 인식되지 않은 확장 필드는 무시해야 합니다. 기록된 집계 인시던트의 경우 aggregationValues가 누락되거나 비어 있을 수 있으므로 수신 측에서 호환 가능하게 처리해야 합니다. 집계 인시던트의 연결된 이벤트 스냅샷 또는 집계 키 값이 변경되면 "집계 변경" 이벤트가 트리거됩니다.
요청 출처 확인¶
타사 수신 서비스는 토큰, 타임스탬프 및 서명을 동시에 확인해야 합니다.
서명 원문은 다음과 같습니다.
여기서 rawRequestBody는 수신된 원시 요청 바이트이며, JSON을 먼저 포맷하거나 재직렬화하지 않아야 합니다. 현재 시간 전후 5분 이내의 요청만 수락하고 eventId로 중복 제거하는 것이 좋습니다.
import hashlib
import hmac
import time
def verify_webhook(request, token):
raw_body = request.get_data(cache=True)
timestamp = request.headers.get("X-Incidents-Timestamp", "")
event_id = request.headers.get("X-Incidents-Event-Id", "")
signature = request.headers.get("X-Incidents-Signature", "")
authorization = request.headers.get("Authorization", "")
if not hmac.compare_digest(authorization, f"Bearer {token}"):
return False
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
return False
message = f"{timestamp}.{event_id}.".encode("utf-8") + raw_body
expected = "v1=" + hmac.new(
token.encode("utf-8"), message, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
boolean verify(byte[] rawBody, String timestamp, String eventId,
String signature, String authorization, String token) throws Exception {
if (!MessageDigest.isEqual(
authorization.getBytes(StandardCharsets.UTF_8),
("Bearer " + token).getBytes(StandardCharsets.UTF_8))) return false;
byte[] prefix = (timestamp + "." + eventId + ".").getBytes(StandardCharsets.UTF_8);
byte[] content = new byte[prefix.length + rawBody.length];
System.arraycopy(prefix, 0, content, 0, prefix.length);
System.arraycopy(rawBody, 0, content, prefix.length, rawBody.length);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(token.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String expected = "v1=" + java.util.HexFormat.of().formatHex(mac.doFinal(content));
return MessageDigest.isEqual(expected.getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8));
}
import (
"crypto/hmac"
"crypto/sha256"
"fmt"
)
func verifyWebhook(rawBody []byte, timestamp, eventID, signature, authorization, token string) bool {
if !hmac.Equal([]byte(authorization), []byte("Bearer "+token)) {
return false
}
content := append([]byte(timestamp+"."+eventID+"."), rawBody...)
mac := hmac.New(sha256.New, []byte(token))
mac.Write(content)
expected := fmt.Sprintf("v1=%x", mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}
전송 및 전송 내역¶
동일한 Webhook이 동일한 인시던트에 대해 짧은 시간 동안 연속적으로 여러 변경 사항을 생성하면 시스템이 이를 하나의 전송으로 병합하고 Payload의 eventTypes에 이번 전송에 포함된 변경 유형을 반환합니다. Payload는 항상 전송 시점의 최신 인시던트 스냅샷을 기준으로 합니다.
시스템은 자동 재시도하지 않습니다. 전송 내역에서 전송 상태, 인시던트 UUID 또는 이벤트 유형별로 전송 결과를 확인할 수 있습니다. 실패 기록에는 응답 코드와 오류 세부 정보가 표시되어 타사 수신 서비스 문제 해결에 도움이 됩니다. 자동 푸시가 실패한 후 다시 전송해야 하는 경우 해당 인시던트 상세 페이지로 이동하여 수동으로 트리거할 수 있습니다.