> ## 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.

# 웹훅 받기 시작하기

> 웹훅 수신 주소를 등록하고, 서명 비밀값을 보관하고, 시험 웹훅으로 연결을 확인하는 방법을 설명합니다.

결제 결과처럼 요청한 뒤에 정해지는 일은 MeterEngine이 도입사 서버로 HTTPS POST 요청을 보내 알립니다. 이것이 웹훅입니다. 콘솔에서는 'API 키와 통보' 화면의 '처리 결과 통보' 구역에서 관리합니다.

## 받을 수 있는 웹훅

| `type` 값 | 콘솔 표기 | 보내는 때 |
| - | - | - |
| `payment_succeeded` | 결제 성공 | 결제 시도가 승인으로 끝났을 때 |
| `payment_failed` | 결제 실패 | 결제 시도가 실패로 끝났을 때 |
| `payment_canceled` | 결제 취소 | 결제 취소가 완료됐을 때 |
| `unbilled_usage_detected` | 과금 제외 사용량 | 매일 한 번, 청구에서 빠지는 사용량이 있을 때 |
| `webhook_test` | 시험 통보 | '시험 통보 보내기'를 눌렀거나 시험 API를 불렀을 때 |

본문 형식과 유형별 `data`는 [웹훅 이벤트 유형과 data](https://docs.meterengine.com/webhooks/events.md)를 보세요.

> [!NOTE]
>
> 인보이스 확정을 알리는 웹훅은 없습니다. 확정 결과는 인보이스 목록 API로 확인합니다.

## 시작하기 전에

* **업무 환경**: 수신 주소는 등록한 업무 환경에 속하고, 그 환경에서 생긴 일만 받습니다. 테스트 환경과 실제 환경에 따로 등록해야 합니다. 업무 환경은 [업무 환경](https://docs.meterengine.com/concepts/environments.md)을 보세요.
* **권한**: 등록, 수정, 보관, 복원, 시험 발송, 수동 재전송에는 admin(전체 관리) 권한이 필요합니다. 목록과 전송 기록 조회는 read(조회)로 됩니다. API 키로도 콘솔로도 할 수 있습니다. 권한은 [화면과 API, 권한](https://docs.meterengine.com/authentication/scopes.md)을 보세요.
* **수신 서버**: 인터넷에서 HTTPS로 접속할 수 있는 주소가 있어야 합니다. 아래 '수신 주소 조건' 절을 먼저 확인하세요.

> [!WARNING]
>
> 테스트 환경에서도 웹훅은 등록한 주소로 실제로 나갑니다. 테스트 환경에 운영 서버 주소를 등록하지 마세요.

## 수신 주소 조건

등록할 때와 보낼 때마다 MeterEngine이 주소를 검사합니다.

| 조건 | 설명 |
| - | - |
| `https://` 로 시작 | `http://` 주소는 등록되지 않습니다. 인증서는 호스트 이름까지 검증합니다 |
| 공개 주소 | 호스트 이름이 가리키는 IP가 모두 공개 인터넷 주소여야 합니다. 루프백(`localhost`, `127.0.0.1`), 사설망(`10.x`, `172.16.x`\~`172.31.x`, `192.168.x`), 링크 로컬 같은 주소는 거절합니다 |
| 등록할 때 DNS 조회가 되어야 함 | 아직 DNS에 없는 호스트 이름은 등록되지 않습니다 |
| 사용자 정보와 `#` 조각 없음 | `https://user:pass@...` 형태나 `#` 뒤 조각이 붙은 주소는 거절합니다. 쿼리 문자열(`?a=b`)은 그대로 붙여 보냅니다 |
| 2048자 이내 | |

조건에 맞지 않으면 `400 invalid_webhook_url`을 받습니다. 개발 PC의 로컬 서버는 등록할 수 없으니 공개 HTTPS 주소를 가진 터널이나 시험 서버를 쓰세요.

## 등록부터 시험까지

**1. 수신 엔드포인트를 만든다**

도입사 서버에 POST 요청을 받는 경로를 하나 만듭니다(예: `https://service.example.com/webhooks/meterengine`). 이 경로는 다음을 지켜야 합니다.

* 요청 본문을 파싱하기 전에 받은 바이트 그대로 보관합니다. 서명은 원문으로 검증합니다.
* `webhook-signature` 헤더로 서명을 검증하고, 맞지 않으면 2xx가 아닌 응답을 돌려줍니다.
* 검증이 끝나면 처리를 뒤로 미루고 바로 2xx로 응답합니다. 요청을 받고 10초 안에 응답 상태를 돌려주지 않으면 MeterEngine은 실패로 보고 다시 보냅니다. 시간 제한은 [전송, 재전송, 중복 처리](https://docs.meterengine.com/webhooks/delivery-and-retries.md)를 보세요.

검증 코드는 [웹훅 서명 검증](https://docs.meterengine.com/webhooks/verify-signatures.md)에 있습니다.

**2. 수신 주소를 등록한다**

> [tab] 콘솔에서
>
> 1. 화면 위 '업무 환경'에서 웹훅을 받을 환경(처음에는 '테스트 환경')을 고릅니다.
> 2. 'API 키와 통보' 화면의 '처리 결과 통보' 구역에서 '수신 주소 등록'을 누릅니다.
> 3. '수신 URL'에 주소를 넣고 '받을 통보'에서 받을 유형을 고릅니다. 기본으로 결제 성공, 결제 실패, 결제 취소, 과금 제외 사용량이 골라져 있습니다.
> 4. 등록하면 서명 비밀값이 한 번 표시됩니다. '비밀값 복사'로 복사해 다음 단계에서 보관합니다.

> [tab] API로
>
> admin 권한 키로 `POST /v1/webhook-endpoints`를 부릅니다. 키가 속한 업무 환경에 등록됩니다.
>
> ```bash curl theme={null}
> curl -X POST https://meterengine.com/v1/webhook-endpoints \
>   -H "Authorization: Bearer me_test_xxxxxxxx" \
>   -H "Content-Type: application/json" \
>   -d '{
>     "url": "https://service.example.com/webhooks/meterengine",
>     "event_types": ["payment_succeeded", "payment_failed", "payment_canceled", "unbilled_usage_detected"],
>     "description": "결제 결과 수신 서버"
>   }'
> ```
>
> ```javascript Node (fetch) theme={null}
> const response = await fetch("https://meterengine.com/v1/webhook-endpoints", {
>   method: "POST",
>   headers: {
>     Authorization: `Bearer ${process.env.METERENGINE_API_KEY}`,
>     "Content-Type": "application/json",
>   },
>   body: JSON.stringify({
>     url: "https://service.example.com/webhooks/meterengine",
>     event_types: ["payment_succeeded", "payment_failed", "payment_canceled", "unbilled_usage_detected"],
>     description: "결제 결과 수신 서버",
>   }),
> });
> const endpoint = await response.json();
> // endpoint.secret 은 이 응답에만 있습니다. 바로 비밀 저장소에 넣으세요.
> ```
>
> | 필드 | 필수 | 설명 |
> | - | - | - |
> | `url` | 예 | 수신 주소. 2048자 이내 |
> | `event_types` | 예 | 받을 `type` 값 목록. 1개에서 20개. 위 표에 없는 값을 넣으면 `400 invalid_webhook_event_type` |
> | `description` | 아니요 | 메모. 1000자 이내 |
>
> 성공하면 `201 Created`와 함께 아래 응답이 옵니다.
>
> ```json 201 Created theme={null}
> {
>   "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
>   "url": "https://service.example.com/webhooks/meterengine",
>   "event_types": ["payment_succeeded", "payment_failed", "payment_canceled", "unbilled_usage_detected"],
>   "enabled": true,
>   "description": "결제 결과 수신 서버",
>   "created_at": "2026-11-05T01:00:00.123456Z",
>   "secret": "whsec_xxxxxxxx",
>   "archived_at": null,
>   "latest_attempt_failed": false
> }
> ```
>
> 응답의 `id`는 시험 발송과 전송 기록 조회에 쓰니 함께 저장합니다.

**3. 서명 비밀값을 보관한다**

> [!WARNING]
>
> 서명 비밀값(`whsec_`로 시작)은 등록 직후 한 번만 보입니다. 목록과 조회 API에도 나오지 않고, 다시 보여 주거나 바꾸는 기능이 없습니다. 잃어버렸으면 이 페이지 아래 '서명 비밀값 바꾸기' 절차로 새로 등록해야 합니다.

비밀값은 수신 서버의 비밀 저장소나 환경 변수에 넣고 코드와 저장소에 남기지 마세요. 수신 주소마다 비밀값이 다릅니다.

**4. 시험 웹훅을 보낸다**

콘솔에서는 'API 키와 통보' 화면의 수신 주소 옆 '발송 내역과 설정'을 열고 '시험 통보 보내기'를 누릅니다. API로는 `POST /v1/webhook-endpoints/{id}/test-events`를 부릅니다(본문 없음).

```bash curl theme={null}
curl -X POST https://meterengine.com/v1/webhook-endpoints/7c9e6679-7425-40de-944b-e07fc1f90ae7/test-events \
  -H "Authorization: Bearer me_test_xxxxxxxx"
```

```json 202 Accepted theme={null}
{
  "webhook_event_id": "9b2f4c1e-3d5a-4f6b-8c7d-1e2f3a4b5c6d"
}
```

`202`는 발송 대기에 넣었다는 뜻이고 전달이 끝났다는 뜻이 아닙니다. 웹훅은 30초 간격으로 도는 발송 작업이 보내므로 수신 서버에 닿기까지 수십 초가 걸릴 수 있습니다. 시험 웹훅은 '받을 통보'에 시험 통보를 고르지 않았어도 이 수신 주소로 갑니다. 발송을 일시 중지했거나 보관한 수신 주소에 보내면 `409 webhook_endpoint_disabled`를 받습니다.

받은 요청의 `webhook-id` 헤더 값은 응답의 `webhook_event_id`와 같습니다.

**5. 전송 결과를 확인한다**

'발송 내역과 설정'을 열면 나오는 '통보 발송 내역' 화면 아래 표에서 시험 통보가 '발송 완료'로 바뀌고 '수신 응답'에 2xx 값이 찍히면 연결이 끝난 것입니다. API로는 `GET /v1/webhook-endpoints/{id}/deliveries`로 봅니다.

'발송 실패'나 '재시도 대기'가 보이면 '실패 사유'를 확인하세요. 사유별 뜻과 다시 보내는 방법은 [전송, 재전송, 중복 처리](https://docs.meterengine.com/webhooks/delivery-and-retries.md)를 보세요.

## 수신 주소 관리

콘솔에서는 'API 키와 통보' 화면의 '처리 결과 통보' 구역에서 수신 주소 옆 '발송 내역과 설정'을 누르면 '통보 발송 내역' 화면이 열립니다. 아래 표의 콘솔 동작은 따로 적지 않으면 이 화면에 있습니다.

| 하려는 일 | 콘솔 | API |
| - | - | - |
| 목록 보기 | '처리 결과 통보' 구역의 목록('사용 중', '보관됨', '전체'로 거르기) | `GET /v1/webhook-endpoints` (`status`는 `active` 기본, `archived`, `all`) |
| 하나 보기 | '통보 발송 내역' 화면 | `GET /v1/webhook-endpoints/{id}` |
| 주소, 받을 유형, 발송 상태 바꾸기 | '수신 주소와 통보 수정' | `PUT /v1/webhook-endpoints/{id}` |
| 발송 멈추기 | '수신 주소와 통보 수정'에서 '발송 상태'를 '발송 일시 중지'로 | `PUT`의 `enabled`를 `false`로 |
| 보관하기 | '수신 주소 보관' | `DELETE /v1/webhook-endpoints/{id}` (`204`, 다시 불러도 `204`) |
| 보관한 주소 되살리기 | '처리 결과 통보' 구역에서 '보관됨'을 고르고 그 주소의 '발송 내역과 설정'을 연 뒤 '수신 주소 복원' | `POST /v1/webhook-endpoints/{id}/restorations` |

목록의 상태 표시는 '발송 중', '일시 중지', '보관됨'입니다. '발송 중'인 주소의 가장 최근 발송 시도가 실패했으면 '최근 발송 실패'가 붙습니다. API의 `latest_attempt_failed`는 일시 중지나 보관 상태여도 기록된 가장 최근 시도가 실패였으면 `true`입니다.

> [!WARNING]
>
> `PUT /v1/webhook-endpoints/{id}`는 값을 통째로 바꿉니다. `url`, `event_types`, `enabled`를 모두 보내야 하고, `description`을 빼면 메모가 지워집니다. 주소를 바꾸지 않았어도 `PUT`을 부를 때마다 등록할 때와 같은 주소 검사를 다시 합니다.

* 발송을 멈추거나 보관한 동안 생긴 일은 그 수신 주소로 보내지 않고, 나중에 다시 켜도 보내지 않습니다. 이미 대기 중이던 전송도 실패로 끝납니다. 자세한 내용은 [전송, 재전송, 중복 처리](https://docs.meterengine.com/webhooks/delivery-and-retries.md)를 보세요.
* 보관한 수신 주소는 고칠 수 없습니다(`409 resource_archived`). 복원한 뒤 고칩니다.
* 복원하면 '일시 중지' 상태로 돌아옵니다. 받을 유형을 확인하고 '발송 사용'으로 바꾼 뒤 시험 웹훅으로 다시 점검하세요. 복원할 때도 주소 검사와 등록 한도를 다시 확인합니다.
* 보관해도 전송 기록은 남습니다.

## 등록 한도

수신 주소는 업무 환경마다 10개까지 등록합니다. 일시 중지한 주소는 세고, 보관한 주소는 세지 않습니다. 한도를 넘으면 `409 webhook_endpoint_limit_exceeded`를 받습니다. 쓰지 않는 주소를 보관하면 자리가 생깁니다.

## 서명 비밀값 바꾸기

비밀값을 바꾸는 API는 없습니다. 비밀값이 새었거나 잃어버렸으면 같은 주소로 수신 주소를 하나 더 등록해 옮깁니다. 같은 URL을 여러 번 등록할 수 있습니다.

**1. 같은 URL로 새 수신 주소를 등록한다**

받을 유형을 기존 주소와 같게 고르고, 새 비밀값을 보관합니다.

**2. 수신 서버가 두 비밀값을 모두 받게 한다**

서명 검증에서 기존 비밀값과 새 비밀값 중 하나라도 맞으면 통과시킵니다. 이 동안에는 같은 웹훅이 두 수신 주소로 한 번씩, 같은 `webhook-id`로 옵니다. `webhook-id`로 중복을 거르고 있으면 한 번만 처리됩니다.

**3. 기존 수신 주소를 보관한다**

새 주소의 전송 기록이 '발송 완료'인 것을 확인한 뒤 기존 주소를 보관하고, 수신 서버에서 기존 비밀값을 지웁니다.

## 업무 환경마다 수신 주소 나누기

수신 주소와 비밀값은 업무 환경마다 따로입니다. 테스트 환경 수신 주소에는 테스트 환경에서 생긴 일만, 실제 환경 수신 주소에는 실제 환경에서 생긴 일만 갑니다. 테스트 환경과 실제 환경의 웹훅을 한 수신 서버에서 받는다면 경로를 나누세요(예: `/webhooks/meterengine/test`, `/webhooks/meterengine/live`). 그래야 서버가 요청을 받자마자 어느 비밀값으로 검증할지 정할 수 있고, 검증 뒤 본문의 `environment`가 그 경로와 같은지도 확인할 수 있습니다.

업무 환경이 '기존 데이터'일 때 등록한 수신 주소는 기존 데이터에서 생긴 일만 받습니다. 새로 연동한다면 테스트 환경과 실제 환경에 등록하세요.

## 방화벽과 요청 헤더

MeterEngine이 보내는 요청은 `POST`이고 다음 헤더를 싣습니다.

| 헤더 | 값 |
| - | - |
| `Content-Type` | `application/json` |
| `User-Agent` | `MeterEngine-Webhooks/1.0` |
| `webhook-id` | 웹훅 하나의 고유 값(UUID). 다시 보내도 같습니다 |
| `webhook-timestamp` | 이번 시도에서 서명한 시각(Unix 초) |
| `webhook-signature` | `v1,`로 시작하는 서명 |

방화벽이나 WAF 규칙에서 `User-Agent: MeterEngine-Webhooks/1.0`을 허용 조건으로 쓸 수 있습니다. 다만 이 헤더는 누구나 흉내 낼 수 있으니 MeterEngine이 보낸 요청인지는 서명으로만 판단하세요. 리다이렉트는 따라가지 않으므로 수신 주소가 3xx를 돌려주면 실패로 처리됩니다.

## 다음 단계

> [card] [웹훅 서명 검증](https://docs.meterengine.com/webhooks/verify-signatures.md)
>
> 받은 요청이 MeterEngine이 보낸 것인지 확인하는 코드

> [card] [웹훅 이벤트 유형과 data](https://docs.meterengine.com/webhooks/events.md)
>
> 본문 형식과 유형별 필드

> [card] [전송, 재전송, 중복 처리](https://docs.meterengine.com/webhooks/delivery-and-retries.md)
>
> 성공 기준, 자동 재전송 일정, 중복 거르기

> [card] [API 참조](https://docs.meterengine.com/api-reference/introduction.md)
>
> 수신 주소 관리 API의 요청과 응답


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