콘텐츠로 이동

인시던트 웹훅 푸시


Webhook은 인시던트의 주요 변경 사항을 타사 시스템으로 푸시하는 데 사용됩니다. 인시던트 정보를 ITSM, 자동화 플랫폼 또는 내부 업무 시스템에 연동하여 타사가 수신된 데이터를 기반으로 티켓을 생성하거나, 상태를 동기화하거나, 자동화 워크플로우를 트리거할 수 있습니다.

인시던트 센터 > 설정 관리 > Webhook에서 Webhook을 생성하고 관리할 수 있습니다.

전제 조건

먼저 POST 요청을 수신할 수 있는 타사 주소를 준비하세요. Webhook은 HTTP 및 HTTPS 주소를 지원합니다. 프로덕션 환경에서는 HTTPS를 사용하는 것이 좋습니다. 보안상의 이유로 내부 네트워크, 루프백 또는 로컬 호스트 주소는 구성할 수 없습니다.

Webhook 구성

  1. Webhook 페이지에서 웹훅 생성을 클릭합니다.
  2. 이름과 타사 수신 URL을 입력합니다.
  3. 자동으로 푸시할 인시던트 이벤트를 선택합니다. | 이벤트 | 설명 | | --- | --- | | 인시던트 생성 | 새 인시던트가 생성될 때 푸시합니다. | | 상태 변경 | 인시던트 상태가 변경될 때 푸시합니다. | | 등급 변경 | 인시던트 등급이 변경될 때 푸시합니다. | | 담당자 변경 | 인시던트 담당자가 변경될 때 푸시합니다. | | 집계 변경 | 집계 인시던트의 연결된 이벤트가 변경될 때 푸시합니다. 인시던트 집계 규칙을 통해 생성된 인시던트에만 적용됩니다. |
  4. 토큰 생성을 클릭합니다. 토큰은 Guance 플랫폼에서만 생성됩니다. 즉시 복사하여 타사 수신 서비스의 키 구성에 안전하게 저장하세요.
  5. 활성화 상태를 설정하고 저장합니다.

자동 트리거 이벤트는 선택하지 않아도 됩니다. Webhook이 활성화되면 자동 트리거 이벤트가 구성되지 않은 경우에도 인시던트 상세 페이지에서 수동 전송을 사용할 수 있습니다.

참고
  • 토큰은 Webhook의 인증 키입니다. 생성 후 플랫폼은 한 번만 평문을 표시하며, 이후 페이지에서는 마스킹된 값만 표시됩니다. 토큰을 다시 생성하면 이전 토큰이 즉시 만료됩니다.
  • Webhook 전송 실패는 인시던트의 생성, 업데이트, 복구 및 종료에 영향을 미치지 않습니다.
  • Webhook은 고정된 요청 방식, 인증 방식 및 시간 제한을 사용하므로 추가 구성이 필요하지 않습니다.

테스트 전송

Webhook을 저장한 후 해당 Webhook의 작업 항목에서 테스트를 클릭하고 이벤트 유형을 선택할 수 있습니다. 시스템은 정식 요청 형식으로 테스트 데이터를 전송하며, Payload의 isTesttrue입니다.

테스트 결과는 전송 내역에서 확인할 수 있습니다. 테스트 전송은 실제 인시던트에 영향을 미치지 않으며 정식 전송 통계에 포함되지 않습니다.

수동 전송

인시던트 상세 페이지에서 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가 누락되거나 비어 있을 수 있으므로 수신 측에서 호환 가능하게 처리해야 합니다. 집계 인시던트의 연결된 이벤트 스냅샷 또는 집계 키 값이 변경되면 "집계 변경" 이벤트가 트리거됩니다.

요청 출처 확인

타사 수신 서비스는 토큰, 타임스탬프 및 서명을 동시에 확인해야 합니다.

서명 원문은 다음과 같습니다.

{timestamp}.{eventId}.{rawRequestBody}

여기서 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 또는 이벤트 유형별로 전송 결과를 확인할 수 있습니다. 실패 기록에는 응답 코드와 오류 세부 정보가 표시되어 타사 수신 서비스 문제 해결에 도움이 됩니다. 자동 푸시가 실패한 후 다시 전송해야 하는 경우 해당 인시던트 상세 페이지로 이동하여 수동으로 트리거할 수 있습니다.

문서 평가

이 페이지가 도움이 되었나요?