> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dgrid.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook 가이드

> HTTPS 수신 서버로 DGrid 이벤트를 받고 서명을 검증하며 테스트, 재시도, 중복 전달을 처리합니다.

Webhook은 지정한 수신 주소로 DGrid 알림을 보냅니다. 연결된 이메일과 독립적으로 동작하므로 이메일 주소가 필요하지 않습니다.

이메일과 Lark 설정은 [알림 설정](/ko/ai-gateway/notifications)을 참고하세요. 이 가이드는 사용자 지정 HTTPS 수신 서버를 다룹니다.

## Webhook 설정

1. <strong>수신 주소 입력</strong>: DGrid의 Webhook 설정에 기술팀 또는 직접 준비한 공개 HTTPS URL을 입력하고 저장하세요.
2. <strong>Webhook 활성화</strong>.
3. <strong>Signing Secret 가져오기</strong>: <strong>Rotate Secret</strong>을 클릭하고 생성된 시크릿을 안전하게 복사하여 수신 서버 설정에 추가하세요. 교체할 때마다 새 시크릿이 생성됩니다. 대기 중인 전달이 끝날 때까지 이전 검증 키도 보관하세요.
4. <strong>이벤트 선택</strong>: 받을 알림 유형을 활성화하세요.
5. <strong>알림 테스트</strong>: 수신 서버가 알림을 받아 서명을 검증하는지 확인하세요.

## 사용 가능한 이벤트

다음 6개 이벤트는 모두 Webhook 설정에서 사용할 수 있습니다. 수신 서버는 JSON 최상위 `eventType`으로 이벤트를 식별합니다. 대소문자, 마침표, 밑줄을 정확히 유지하세요.

| 알림 | 이벤트 식별자(`eventType`) | 수신 시점 |
| - | - | - |
| 충전 잔액 부족 | `balance.low` | 충전 잔액이 계정 알림 기준 이하로 내려갈 때입니다. |
| 충전 잔액 소진 | `balance.depleted` | 충전 잔액이 0 또는 음수일 때입니다. |
| Auto Deposit 완료 | `auto_recharge.succeeded` | 자동 충전이 확인되고 잔액에 반영될 때입니다. |
| Auto Deposit 실패 | `auto_recharge.failed` | 자동 충전 시도가 실패할 때입니다. |
| 구독 활성화 | `subscription.activated` | 구독이 활성화되거나 요금제 변경이 완료될 때입니다. |
| 구독 잔액 부족 | `subscription.balance_low` | 월간 구독 잔액이 15% 이하로 내려갈 때입니다. |

충전 잔액과 구독 잔액은 별개입니다. 구독 잔액 부족 알림은 구독별 UTC 월에 최대 한 번 전송되며 별도의 구독 잔액 소진 이벤트는 없습니다. Auto Deposit 실패는 실제 시도의 실패이며 시도가 생성되기 전의 자금 또는 승인 경고를 의미하지 않습니다.

## 수신 요청

DGrid는 저장된 URL에 `Content-Type: application/json`으로 HTTP `POST` 요청을 보냅니다. 모든 알림은 같은 외부 JSON 구조를 사용합니다.

```json theme={null}
{
  "id": "1f3c489c-5ca1-41f4-872e-c10b2b47207a",
  "eventType": "balance.low",
  "createdAt": "2026-10-13T08:30:00Z",
  "data": {
    "eventType": "balance.low",
    "balance": "4.25",
    "test": false
  },
  "test": false
}
```

| 필드 | 유형 | 설명 |
| - | - | - |
| `id` | string | 전달 UUID이며 `DGrid-Event-Id`와 같습니다. 재시도 시 유지되므로 중복 제거에 사용하세요. |
| `eventType` | string | 위 6개 식별자 중 하나입니다. 이 최상위 필드로 알림을 분기하세요. |
| `createdAt` | string | 이번 전달 시도의 UTC RFC 3339 타임스탬프입니다. 재시도 시 달라질 수 있으며 실제 이벤트 발생 시각과 같지 않을 수 있습니다. |
| `data` | object | 아래에 설명된 이벤트별 데이터입니다. |
| `test` | boolean | 테스트는 `true`, 실제 알림은 `false`입니다. |

## 이벤트 데이터

다음 예시는 공통 구조의 `data`만 대체합니다. ID, 시각, 잔액은 예시 값입니다.

### 충전 잔액 부족: `balance.low`

```json theme={null}
{
  "eventType": "balance.low",
  "balance": "4.25",
  "test": false
}
```

### 충전 잔액 소진: `balance.depleted`

```json theme={null}
{
  "eventType": "balance.depleted",
  "balance": "-1",
  "test": false
}
```

두 이벤트의 `eventType`은 문자열이고 `test`는 불리언입니다. `balance`는 USD 단위의 부호 있는 십진수 문자열이며 0이나 음수일 수 있습니다. 구독 잔액을 제외한 충전 잔액입니다. 정밀도를 유지하고 부동소수점으로 변환하거나 이메일 표시용 반올림 또는 자르기 규칙을 적용하지 마세요.

### Auto Deposit 완료: `auto_recharge.succeeded`

```json theme={null}
{
  "attemptId": "7e6956ab-f9cb-4dc0-b793-6e5b6237bb48",
  "state": "confirmed",
  "occurredAt": "2026-10-13T08:29:45Z"
}
```

### Auto Deposit 실패: `auto_recharge.failed`

```json theme={null}
{
  "attemptId": "7304a355-2a63-4c23-9121-7b615d7c410f",
  "state": "failed",
  "occurredAt": "2026-10-13T08:29:45Z"
}
```

세 필드는 모두 문자열입니다. `attemptId`는 자동 충전 시도 ID이고 `state`는 각각 `confirmed` 또는 `failed`로 고정됩니다. `occurredAt`은 UTC RFC 3339 이벤트 시각이며 소수점 이하 초를 포함할 수 있습니다. 데이터에는 이 세 필드만 있습니다. 내부 `eventType`, `test`, 금액, 거래 해시, 지갑, 실패 사유는 없습니다.

### 구독 활성화: `subscription.activated`

```json theme={null}
{
  "eventType": "subscription.activated",
  "occurredAt": "2026-10-13T08:29:45Z",
  "sourceId": "d45ae7a2-401a-4980-9371-736eb51aeacd"
}
```

`sourceId`는 구독 UUID입니다.

### 구독 잔액 부족: `subscription.balance_low`

```json theme={null}
{
  "eventType": "subscription.balance_low",
  "occurredAt": "2026-10-13T08:29:45Z",
  "sourceId": "d45ae7a2-401a-4980-9371-736eb51aeacd:2026-10"
}
```

`sourceId`는 구독 UUID와 UTC 월을 `<subscription UUID>:YYYY-MM` 형식으로 결합합니다. 구독 이벤트의 세 필드는 모두 문자열이며 `occurredAt`은 UTC RFC 3339 타임스탬프입니다. 요금제 이름, 구독 날짜, 할당량, 비율, 지갑은 포함되지 않습니다. 이메일에 표시된 정보가 모두 포함된다고 가정하지 마세요.

## 테스트 알림

테스트 알림은 최상위 `test`가 `true`입니다. 모든 이벤트는 같은 세 필드의 테스트 `data`를 사용하며 `data.eventType`은 선택한 이벤트와 최상위 `eventType`과 일치합니다. Auto Deposit 완료를 선택하면 다음과 같습니다.

```json theme={null}
{
  "id": "55555555-5555-4555-8555-555555555555",
  "eventType": "auto_recharge.succeeded",
  "createdAt": "2026-10-13T08:00:00Z",
  "data": {
    "eventType": "auto_recharge.succeeded",
    "balance": "",
    "test": true
  },
  "test": true
}
```

서명을 검증하고 응답하되, 실제 이벤트 스키마 검증이나 업무 데이터 처리 전에 최상위 `test`를 확인하세요. 테스트에는 `attemptId`, `sourceId` 같은 실제 필드가 없으며 빈 `balance`는 0을 의미하지 않습니다. 테스트로 잔액을 변경하거나 충전을 기록하거나 구독을 활성화하지 마세요.

## 서명 검증

요청에는 네 개의 관련 헤더가 포함됩니다.

| 헤더 | 값 |
| - | - |
| `DGrid-Event-Id` | 최상위 `id`와 일치하는 전달 UUID입니다. |
| `DGrid-Timestamp` | 문자열로 표시된 초 단위 Unix 타임스탬프입니다. |
| `DGrid-Signature` | `sha256=` 뒤에 소문자 16진수 HMAC 다이제스트가 붙습니다. |
| `DGrid-Signature-Version` | `v1`이며 서명 프로토콜 버전입니다. 키 버전이 아닙니다. |

헤더 이름은 대소문자를 구분하지 않습니다. 별도의 서명 키 버전 헤더는 없습니다.

<strong>Rotate Secret</strong>에 표시되는 Signing Secret은 32바이트 키의 패딩 없는 base64url 인코딩입니다. HMAC 계산 전에 바이트로 디코딩하세요. 표시된 시크릿 텍스트를 키로 직접 사용하지 마세요.

```text theme={null}
key = base64url_decode(Signing Secret)
message = UTF8(DGrid-Timestamp + ".") + exact raw request body bytes
expected = "sha256=" + lowercase_hex(HMAC_SHA256(key, message))
```

요청 본문의 원본 바이트를 그대로 보존하고 JSON 파싱 전에 서명을 검증하세요. JSON을 파싱하고 다시 직렬화하면 바이트가 변경되어 검증에 실패할 수 있습니다.

예상 서명과 `DGrid-Signature`를 상수 시간 비교로 확인하고 버전 `v1`을 요구하세요. 재전송 공격을 제한하려면 5분의 타임스탬프 허용 범위를 권장합니다. 수신 서버의 시계를 동기화하세요. 검증 후 JSON을 파싱하고 `DGrid-Event-Id`와 `id`가 일치하는지 확인하세요.

## 전달에 응답하기

검증된 알림을 영구 저장한 후 <strong>5초 이내</strong>에 HTTP `2xx`를 반환하세요. 예를 들어 영구 큐에 저장한 다음 다음 응답을 반환합니다.

```http theme={null}
HTTP/1.1 204 No Content
```

JSON 응답 본문이나 래퍼는 필요하지 않습니다. DGrid는 HTTP 상태로 수락 여부를 판단합니다. HTTP `200`의 JSON 본문이 오류를 표시해도 수락으로 간주합니다.

`2xx`가 아닌 응답, 네트워크 실패, 시간 초과는 재시도를 유발합니다. 최초 시도를 포함하여 <strong>총 최대 5회</strong>입니다. 리디렉션은 따라가지 않습니다. 오래 걸리는 업무 처리는 알림을 영구 저장한 후 진행하세요.

전달은 중복될 수 있으며 순서를 보장하지 않습니다. 재시도 시 유지되는 최상위 `id`로 중복을 제거하세요. 이미 수락한 중복 요청에도 `2xx`를 반환하고 업무 처리를 반복하지 마세요. 재시도 시 타임스탬프, 서명, 원본 바이트는 달라질 수 있습니다. 이 값이나 `createdAt`을 중복 제거 또는 이벤트 순서 판단에 사용하지 마세요.

## URL 변경, 시크릿 교체, 비활성화

대기 중인 전달은 큐에 들어갈 당시의 URL과 서명 키를 유지합니다. URL이나 시크릿을 변경하면 대기 전달이 모두 끝날 때까지 이전 수신 서버와 검증 키를 유지하세요.

Webhook을 비활성화해도 대기 중인 전달은 취소되지 않습니다. 수신 서버는 해당 요청을 계속 받아 처리해야 할 수 있습니다.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.