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

# 웹훅 이벤트 유형과 data

> 모든 웹훅에 공통인 본문 형식과 유형별 data 필드, 유형마다 보내는 때를 설명합니다.

모든 웹훅 본문은 같은 틀에 `type`별 `data`를 담은 JSON입니다. 수신 서버는 서명을 확인한 뒤 `type`으로 분기합니다. 서명 확인은 [웹훅 서명 검증](https://docs.meterengine.com/webhooks/verify-signatures.md)을 보세요.

> [!NOTE]
>
> 이 페이지의 `data`는 지금 보내는 형태입니다. 필드가 더해질 수 있으므로 모르는 필드는 무시하고, 필드 순서에 기대지 마세요. 모르는 `type`을 받으면 처리하지 말고 2xx로 응답하세요.

## 공통 본문

**environment** `string` *required*

웹훅이 생긴 업무 환경입니다. `test`(테스트 환경), `live`(실제 환경), 또는 업무 환경이 '기존 데이터'면 `legacy`입니다. 수신 주소는 등록한 환경의 웹훅만 받으므로 이 값은 언제나 그 수신 주소의 환경과 같습니다. 다르면 처리하지 마세요.

**type** `string` *required*

웹훅 유형입니다. 아래 표의 값 가운데 하나입니다.

**timestamp** `string` *required*

MeterEngine이 이 웹훅을 만든 시각입니다. 시간대 표시가 붙은 ISO 8601 문자열이니 날짜 파서로 읽으세요. 다시 보내도 바뀌지 않습니다. 서명에 쓰는 `webhook-timestamp` 헤더(이번 시도의 시각)와는 다른 값입니다.

**data** `object` *required*

`type`마다 다른 내용입니다. 아래 유형별 절을 보세요.

`environment`와 결제 웹훅 `data`의 `mode`는 뜻이 다릅니다. `environment`는 업무 환경이고, `mode`는 그 결제를 처리한 결제 모드(`test`는 테스트 결제, `live`는 실결제)입니다. 테스트 환경의 결제는 언제나 `mode`가 `test`이고 실제 환경의 결제는 언제나 `live`입니다. 결제 모드는 [테스트 결제와 실결제](https://docs.meterengine.com/payments/test-and-live.md)를 보세요.

## 유형과 보내는 때

| `type` | 콘솔 표기 | 보내는 때 |
| - | - | - |
| `payment_succeeded` | 결제 성공 | 결제 시도가 승인(`done`)으로 끝났을 때. 바로 승인된 결제, 결과 확인 중이던 결제, 자동 재시도로 다시 한 결제 모두 해당합니다 |
| `payment_failed` | 결제 실패 | 결제 시도가 실패(`failed`)로 끝났을 때. 시도마다 한 번씩 옵니다 |
| `payment_canceled` | 결제 취소 | 결제 취소가 완료됐을 때. 부분 취소는 취소할 때마다 한 번씩 옵니다 |
| `unbilled_usage_detected` | 과금 제외 사용량 | 매일 09:00 KST에 지난달과 이번 달을 살펴, 청구에서 빠지는 사용량이 있는 달마다 하나씩 |
| `webhook_test` | 시험 통보 | 수신 주소에 시험 발송을 요청했을 때. 그 수신 주소로만 갑니다 |

수신 주소의 '받을 통보'(`event_types`)에 고른 유형만 받습니다. `webhook_test`는 고르지 않아도 시험 발송한 수신 주소로 갑니다.

다음 일에는 웹훅이 없습니다.

* **인보이스 확정.** 확정 결과는 인보이스 목록 API(`GET /v1/invoices`)로 확인합니다. 확정은 [인보이스 수동 확정](https://docs.meterengine.com/billing/finalize-invoices.md)을 보세요.
* **결제 요청 접수와 결과 확인 중(`pending`).** 결과가 승인이나 실패로 정해질 때 `payment_succeeded`나 `payment_failed`가 옵니다.
* **결제 취소가 실패했을 때.** 취소 결과는 `GET /v1/payment-attempts/{id}/cancellations`로 확인합니다.

## 결제 성공과 결제 실패

`payment_succeeded`와 `payment_failed`의 `data`는 같은 모양입니다.

**payment\_attempt\_id** `string (UUID)`

결제 시도의 id입니다. 자동 재시도는 새 시도를 만들므로 같은 인보이스라도 시도마다 값이 다릅니다.

**invoice\_id** `string (UUID)`

결제한 인보이스의 id입니다.

**customer\_id** `string (UUID)`

인보이스의 고객 id(MeterEngine 고객 id)입니다.

**amount** `integer`

결제 시도 금액(원)입니다.

**mode** `string`

결제 모드입니다. `test` 또는 `live`.

```json payment_succeeded (보기 좋게 줄을 나눈 것) theme={null}
{
  "data": {
    "mode": "test",
    "amount": 12340,
    "invoice_id": "5f8e2d1c-4b3a-4c9d-8e7f-6a5b4c3d2e1f",
    "customer_id": "1d4c8b2a-6e5f-4a3b-9c8d-7e6f5a4b3c2d",
    "payment_attempt_id": "8e7d6c5b-4a3f-4e2d-9c1b-0a9f8e7d6c5b"
  },
  "type": "payment_succeeded",
  "timestamp": "2026-11-05T01:20:00.481206Z",
  "environment": "test"
}
```

`payment_failed`에는 실패 사유가 실리지 않습니다. 사유는 `GET /v1/invoices/{id}/payment-attempts` 응답의 `failure_code`로 확인합니다. 실패한 결제는 조건에 맞으면 MeterEngine이 자동으로 다시 시도하므로, `payment_failed` 뒤에 같은 `invoice_id`로 `payment_succeeded`가 올 수 있습니다. 재시도 조건은 [결제 실패와 재시도](https://docs.meterengine.com/payments/retries.md)를 보세요.

## 결제 취소

**cancellation\_id** `string (UUID)`

이번 취소의 id입니다.

**payment\_attempt\_id** `string (UUID)`

취소한 결제 시도의 id입니다.

**invoice\_id** `string (UUID)`

인보이스 id입니다.

**customer\_id** `string (UUID)`

고객 id입니다.

**amount** `integer`

이번 취소에서 취소한 금액(원)입니다. 결제 금액 전체나 누적 취소액이 아닙니다.

**mode** `string`

결제 모드입니다. `test` 또는 `live`.

```json payment_canceled (보기 좋게 줄을 나눈 것) theme={null}
{
  "data": {
    "mode": "test",
    "amount": 5000,
    "invoice_id": "5f8e2d1c-4b3a-4c9d-8e7f-6a5b4c3d2e1f",
    "customer_id": "1d4c8b2a-6e5f-4a3b-9c8d-7e6f5a4b3c2d",
    "cancellation_id": "3c2b1a0f-9e8d-4c7b-8a6f-5e4d3c2b1a0f",
    "payment_attempt_id": "8e7d6c5b-4a3f-4e2d-9c1b-0a9f8e7d6c5b"
  },
  "type": "payment_canceled",
  "timestamp": "2026-11-06T02:05:11.093517Z",
  "environment": "test"
}
```

결제를 취소해도 인보이스 금액은 바뀌지 않습니다. 취소 규칙은 [결제 취소](https://docs.meterengine.com/payments/cancellations.md)를 보세요.

## 과금 제외 사용량

`unbilled_usage_detected`는 사건 통보가 아니라 상태 보고입니다. 매일 09:00 KST에 업무 환경마다 지난달과 이번 달을 따로 살펴, 아래 항목 가운데 하나라도 해당하는 달마다 웹훅을 하나씩 보냅니다. 해당하는 것이 없는 달은 보내지 않습니다. 그 업무 환경에 이 유형을 받는 수신 주소가 하나도 없거나 모두 일시 중지 상태면 보내지 않습니다. 예약 작업 시각은 [시간 규칙](https://docs.meterengine.com/concepts/time-rules.md)에 모았습니다.

> [!WARNING]
>
> 상태가 그대로면 다음 날 같은 내용이 새 `webhook-id`로 다시 옵니다. `webhook-id`로는 이 반복이 걸러지지 않으니, 같은 알림을 매일 다시 띄우지 않으려면 `month`와 내용으로 직접 비교하세요.

**month** `string`

살펴본 청구 월(`YYYY-MM`, KST)입니다.

**cutoff\_at** `string`

이 보고가 센 기준 시각입니다. 이번 달은 보고를 만든 시각, 지난달은 그 달이 끝난 시각입니다.

**unmatched\_type** `object`

그 달에 맞는 미터가 없는 `type`으로 들어온 이벤트입니다.

**필드**

**event\_count** `integer`

해당 이벤트 수

**types** `object[]`

`type`(이벤트 type 값)과 `event_count`의 목록. 이벤트 수가 많은 것부터 최대 20개를 싣고, 위의 `event_count`는 목록에 없는 `type`까지 합친 수입니다

**non\_numeric\_value** `object`

미터가 집계하는 속성의 값이 없거나 숫자가 아닌 이벤트입니다. `count` 미터는 세지 않고, `count_distinct` 미터는 값이 없거나 숫자, 문자열, 참거짓이 아닐 때만 셉니다. 판정은 미터마다 하므로 한 이벤트가 여러 미터에 걸리면 `billable_metrics`에는 미터마다 세고 `event_count`에는 한 번만 셉니다. `negative_quantity`도 같습니다.

**필드**

**event\_count** `integer`

해당 이벤트 수

**billable\_metrics** `object[]`

`billable_metric_code`(미터 code)와 `event_count`의 목록

**future\_timestamp** `object`

`timestamp`가 `cutoff_at`보다 뒤인 이벤트입니다. 그 시각이 지나면 집계에 들어갑니다.

**필드**

**event\_count** `integer`

해당 이벤트 수

**earliest\_occurred\_at** `string | null`

가장 이른 이벤트 시각. 없으면 `null`

**latest\_occurred\_at** `string | null`

가장 늦은 이벤트 시각. 없으면 `null`

**negative\_quantity** `object`

집계 속성 값이 음수인 이벤트입니다(`count`, `count_distinct` 미터는 세지 않습니다). 거절되거나 집계에서 빠지지 않고 그대로 집계됩니다.

**필드**

**event\_count** `integer`

해당 이벤트 수

**billable\_metrics** `object[]`

`billable_metric_code`와 `event_count`의 목록

**negative\_usages** `object[]`

집계한 합이 음수라 금액을 0원으로 보고 인보이스 라인에서 뺀 조합입니다. 각 항목은 아래 `unpriced_usages`와 같은 필드를 가집니다.

**unpriced\_usages** `object[]`

사용량은 있지만 단가가 없어 금액에서 빠진 조합입니다.

**항목 필드**

**customer\_id** `string (UUID)`

고객 id

**billable\_metric\_code** `string`

미터 code

**target\_property** `string`

미터가 집계하는 속성 이름

**dimension\_values** `object`

차원 속성 값의 조합

**quantity** `number`

그 조합의 집계량

```json unbilled_usage_detected (보기 좋게 줄을 나눈 것) theme={null}
{
  "data": {
    "month": "2026-11",
    "cutoff_at": "2026-11-06T09:00:01.402113+09:00",
    "unmatched_type": {
      "types": [{ "type": "llm.tokens.v2", "event_count": 3 }],
      "event_count": 3
    },
    "negative_usages": [],
    "unpriced_usages": [
      {
        "quantity": 15000,
        "customer_id": "1d4c8b2a-6e5f-4a3b-9c8d-7e6f5a4b3c2d",
        "target_property": "tokens",
        "dimension_values": { "model": "large" },
        "billable_metric_code": "input_tokens"
      }
    ],
    "future_timestamp": {
      "event_count": 0,
      "latest_occurred_at": null,
      "earliest_occurred_at": null
    },
    "negative_quantity": {
      "event_count": 1,
      "billable_metrics": [{ "event_count": 1, "billable_metric_code": "input_tokens" }]
    },
    "non_numeric_value": {
      "event_count": 0,
      "billable_metrics": []
    }
  },
  "type": "unbilled_usage_detected",
  "timestamp": "2026-11-06T00:00:01.730264Z",
  "environment": "test"
}
```

같은 항목은 `GET /v1/unbilled-events?month=YYYY-MM`(`unmatched_type`, `non_numeric_value`, `future_timestamp`, `negative_quantity`)과 `GET /v1/invoices/draft?month=YYYY-MM`의 고객별 `negative_usages`, `unpriced_usages`로도 볼 수 있습니다. 청구 예정액 응답에는 그 달을 이미 확정한 고객이 나오지 않으므로, 웹훅의 `negative_usages`, `unpriced_usages`와 고객 수가 다를 수 있습니다. 항목별 원인과 고치는 방법은 [이벤트가 청구에 안 보일 때](https://docs.meterengine.com/troubleshooting/events-not-billed.md)를 보세요.

## 시험 통보

**message** `string`

`Webhook connection test` 고정 문자열입니다.

```json webhook_test theme={null}
{"data": {"message": "Webhook connection test"}, "type": "webhook_test", "timestamp": "2026-11-05T01:00:02.118374Z", "environment": "test"}
```

## 받은 웹훅 처리하기

웹훅은 무엇이 바뀌었는지 알리는 신호로 쓰고, 상태를 정하는 근거로 쓰지 마세요. 순서가 보장되지 않고 같은 웹훅이 두 번 올 수 있기 때문입니다.

**1. 서명을 확인하고 중복을 거른다**

[웹훅 서명 검증](https://docs.meterengine.com/webhooks/verify-signatures.md)대로 확인하고, 이미 처리한 `webhook-id`면 건너뜁니다.

**2. type으로 분기한다**

모르는 `type`은 처리하지 않고 2xx로 응답합니다.

**3. 해당 자원을 API로 다시 조회한다**

결제 웹훅이면 `GET /v1/invoice-payments?month=YYYY-MM`(인보이스별 수납 상태)나 `GET /v1/invoices/{id}/payment-attempts`(결제 시도 목록)로 지금 상태를 읽고, 그 결과로 도입사 시스템을 갱신합니다.

재전송과 순서 문제는 [전송, 재전송, 중복 처리](https://docs.meterengine.com/webhooks/delivery-and-retries.md)를 보세요.


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