> For the complete documentation index see https://domscan.net/llms.txt. Markdown versions of every docs page are available by appending .md to the page URL.

Source: https://domscan.net/ko/docs/watchlist

# 도메인 관찰 목록

DomScan API로 도메인의 만료 및 가용성 변경을 모니터링합니다. 등록된 도메인이 사용 가능해지면 알림을 받습니다.

도메인의 만료 및 가용성 변경을 모니터링합니다. 등록된 도메인이 사용 가능해지면 알림을 받습니다.

**모니터**

Google으로 로그인하여 이메일 알림, 웹훅 등을 포함한 개인화된 도메인 감시 목록에 접근합니다.

이메일, Telegram, Slack, Discord 또는 Webhook을 통해 알림을 받습니다.

[모니터](https://domscan.net/ko/monitor)
[설정](https://domscan.net/ko/monitor/settings)
[브랜드 모니터링](https://domscan.net/ko/brand-monitor-api)

GET /v1/watchlist

관찰 목록의 모든 도메인을 조회합니다.

#### 예제 응답

```json
{
                "watchlist": [
                {
                "domain": "startup.com",
                "added_at": "2024-01-15T10:30:00Z",
                "status": "registered",
                "expiry_date": "2025-01-15T00:00:00Z",
                "days_until_expiry": 287,
                "last_checked": "2024-03-01T12:00:00Z",
                "notify_email": "alerts@example.com",
                "check_count": 4
                }
                ],
                "total": 1,
                "updated_at": "2024-03-01T12:00:00Z"
                }
```

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `has_more` | boolean |
| `next_cursor` | string \| null |
| `watchlist[]` | object[] |
| `watchlist[]` | object |
| `watchlist[].domain` | string |
| `watchlist[].added_at` | string |
| `watchlist[].status` | string |
| `watchlist[].last_checked` | string |
| `watchlist[].expiry_date` | string |
| `watchlist[].days_until_expiry` | integer |
| `watchlist[].notify_email` | string |
| `watchlist[].check_count` | integer |
| `total` | integer |
| `updated_at` | string |

POST /v1/watchlist

관찰 목록에 도메인을 추가합니다.

#### 요청 본문

```json
{
                "domain": "mydomain.com",
                "notify_email": "alerts@example.com"
                }
```

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `success` | boolean |
| `message` | string |
| `watchlist[]` | object[] |
| `watchlist[]` | object |
| `watchlist[].domain` | string |
| `watchlist[].added_at` | string |
| `watchlist[].status` | string |
| `watchlist[].last_checked` | string |
| `watchlist[].expiry_date` | string |
| `watchlist[].days_until_expiry` | integer |
| `watchlist[].notify_email` | string |
| `watchlist[].check_count` | integer |
| `total` | integer |

#### 예제 응답

```json
{
  "success": true,
  "message": "Added example.org to watchlist",
  "watchlist": [
    {
      "domain": "example.com",
      "added_at": "2026-06-02T09:12:00.000Z",
      "last_checked": "2026-10-09T03:00:12.000Z",
      "status": "expiring_soon",
      "expiry_date": "2026-12-18T14:22:08Z",
      "days_until_expiry": 70,
      "notify_email": "alerts@example.com"
    },
    {
      "domain": "example.org",
      "added_at": "2026-10-09T10:15:30.000Z",
      "status": "unknown",
      "notify_email": "alerts@example.com"
    }
  ],
  "total": 2
}
```

DELETE /v1/watchlist?domain=mydomain.com

관심 목록에서 제거

```bash
curl -X DELETE -H "X-API-Key: your-api-key" "https://domscan.net/v1/watchlist?domain=mydomain.com"
```

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `success` | boolean |
| `message` | string |
| `domain` | string |
| `watchlist[]` | object[] |
| `watchlist[]` | object |
| `watchlist[].domain` | string |
| `watchlist[].added_at` | string |
| `watchlist[].status` | string |
| `watchlist[].last_checked` | string |
| `watchlist[].expiry_date` | string |
| `watchlist[].days_until_expiry` | integer |
| `watchlist[].notify_email` | string |
| `watchlist[].check_count` | integer |
| `total` | integer |

#### 예제 응답

```json
{
  "success": true,
  "message": "Request completed",
  "domain": "example.com",
  "watchlist": [
    {
      "domain": "example.com",
      "added_at": "2026-08-27T12:00:00Z",
      "status": "registered",
      "last_checked": "2026-08-27T12:00:00Z",
      "expiry_date": "2026-08-27T12:00:00Z",
      "days_until_expiry": 1,
      "notify_email": "user@example.com",
      "check_count": 1
    }
  ],
  "total": 1
}
```

GET /v1/watchlist/expiring?days=30

곧 만료

#### 예제 응답

```json
{
                "expiring_domains": [
                {
                "domain": "renewal-window.com",
                "status": "expiring_soon",
                "expiry_date": "2026-05-12T00:00:00Z",
                "days_until_expiry": 28,
                "check_count": 11
                }
                ],
                "total": 1,
                "threshold_days": 30
                }
```

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `has_more` | boolean |
| `next_cursor` | string \| null |
| `expiring_domains[]` | object[] |
| `expiring_domains[]` | object |
| `expiring_domains[].domain` | string |
| `expiring_domains[].added_at` | string |
| `expiring_domains[].status` | string |
| `expiring_domains[].last_checked` | string |
| `expiring_domains[].expiry_date` | string |
| `expiring_domains[].days_until_expiry` | integer |
| `expiring_domains[].notify_email` | string |
| `expiring_domains[].check_count` | integer |
| `total` | integer |
| `threshold_days` | integer |

GET /v1/watchlist/available

사용 가능

페이지네이션 지원: `limit`(1~100)을 보내고 `next_cursor`를 `cursor`로 다시 전달하세요. 응답에는 `has_more`와 `next_cursor`가 포함됩니다. `limit`이 없으면 전체 목록이 반환됩니다. [페이지네이션](https://domscan.net/docs/response-format#pagination)을 참고하세요.

#### 예제 응답

```json
{
                "available_domains": [
                {
                "domain": "startupdrop.ai",
                "status": "available",
                "last_checked": "2026-04-14T08:30:00Z",
                "check_count": 18
                }
                ],
                "total": 1,
                "message": "관심 목록의 이러한 도메인이 이제 등록 가능합니다!"
                }
```

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `has_more` | boolean |
| `next_cursor` | string \| null |
| `available_domains[]` | object[] |
| `available_domains[]` | object |
| `available_domains[].domain` | string |
| `available_domains[].added_at` | string |
| `available_domains[].status` | string |
| `available_domains[].last_checked` | string |
| `available_domains[].expiry_date` | string |
| `available_domains[].days_until_expiry` | integer |
| `available_domains[].notify_email` | string |
| `available_domains[].check_count` | integer |
| `total` | integer |
| `message` | string |

### 알림 웹훅

모니터링 알림을 서명된 JSON POST 요청으로 내 HTTPS 엔드포인트에서 받을 수 있습니다. 엔드포인트는 API 키로 관리하거나, 로그인한 상태에서 모니터 설정에서 관리할 수 있습니다.

#### 이벤트 유형

| 모니터 | 이벤트 |
| --- | --- |
| 감시 목록의 가용성, 만료, 상태 | `domain.available`, `domain.expiring_30`, `domain.expiring_14`, `domain.expiring_7`, `domain.expiring_1`, `domain.status_change` |
| 도메인 변경 모니터링 | `domain.dns_changed`, `domain.certificate_changed`, `domain.registration_changed`, `domain.hosting_changed`, `domain.technology_changed`, `domain.email_security_changed` |
| 브랜드 모니터링(유사 도메인 신규 등록) | `brand.threat_detected` |
| 관심 목록 알림 구독 (인증서, 가격, DNS 레코드, 유사 도메인) | `ssl.expiring`, `ssl.expired`, `ssl.changed`, `price.dropped`, `dns.changed`, `typosquat.detected` |
| 비동기 작업(무료, notify_webhook으로 생성한 작업에만 해당) | `job.completed`, `job.failed`, `job.cancelled` |
| 테스트 이벤트(무료) | `webhook.test` |

[웹훅 알림 API 가이드 읽기](https://domscan.net/ko/webhook-alerts-api)

#### 요금

2xx 응답으로 엔드포인트에 전달된 알림은 건당 2 크레딧입니다. 크레딧은 첫 번째 시도 직전에 예약되며, 전달이 실패하거나 취소되거나 엔드포인트가 삭제되면 환불됩니다. 테스트 이벤트는 무료입니다. 잔액이 부족하면 알림을 건너뛰며 요금은 청구되지 않습니다.

POST /v1/webhooks

HTTPS 엔드포인트를 등록합니다. URL은 공개 주소로 해석되어야 합니다. events를 생략하거나 ["*"]를 전달하면 모든 이벤트 유형을 받습니다. 서명 시크릿은 이 응답에서만 반환됩니다.

#### 예제 요청

```bash
curl -X POST "https://domscan.net/v1/webhooks" \
  -H "X-API-Key: $DOMSCAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/domscan",
    "events": ["domain.expiring_30", "domain.available", "domain.dns_changed"]
  }'
```

#### 예제 응답

```json
{
  "endpoint": {
    "id": "whe_3f1c0a9b8e7d6c5b4a39281706f5e4d3",
    "url": "https://hooks.example.com/domscan",
    "events": ["domain.available", "domain.expiring_30", "domain.dns_changed"],
    "status": "active",
    "secret_hint": "9c2e"
  },
  "secret": "whsec_..."
}
```

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `endpoint` | object |
| `endpoint.id` | string |
| `endpoint.url` | string |
| `endpoint.description` | string \| null |
| `endpoint.events[]` | string[] |
| `endpoint.status` | string |
| `endpoint.disabled_reason` | string \| null |
| `endpoint.consecutive_failures` | integer |
| `endpoint.secret_hint` | string |
| `endpoint.previous_secret_expires_at` | string \| null |
| `endpoint.secret_rotated_at` | string \| null |
| `endpoint.last_success_at` | string \| null |
| `endpoint.last_failure_at` | string \| null |
| `endpoint.last_error` | string \| null |
| `endpoint.created_at` | string |
| `endpoint.updated_at` | string |
| `endpoint.disabled_at` | string \| null |
| `secret` | string |

GET /v1/webhooks

엔드포인트를 상태, 구독 중인 이벤트, 실패 횟수, 시크릿의 마지막 네 글자와 함께 나열하며, 사용 가능한 이벤트 유형과 전달 정책도 함께 반환합니다.

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `endpoints[]` | object[] |
| `endpoints[]` | object |
| `endpoints[].id` | string |
| `endpoints[].url` | string |
| `endpoints[].description` | string \| null |
| `endpoints[].events[]` | string[] |
| `endpoints[].status` | string |
| `endpoints[].disabled_reason` | string \| null |
| `endpoints[].consecutive_failures` | integer |
| `endpoints[].secret_hint` | string |
| `endpoints[].previous_secret_expires_at` | string \| null |
| `endpoints[].secret_rotated_at` | string \| null |
| `endpoints[].last_success_at` | string \| null |
| `endpoints[].last_failure_at` | string \| null |
| `endpoints[].last_error` | string \| null |
| `endpoints[].created_at` | string |
| `endpoints[].updated_at` | string |
| `endpoints[].disabled_at` | string \| null |
| `available_events[]` | string[] |
| `all_events` | string |
| `test_event` | string |
| `policy` | object |
| `policy.secret_rotation_grace_seconds` | integer |
| `policy.secret_rotation_credits` | integer |
| `policy.credits_per_delivered_alert` | integer |
| `policy.test_event_credits` | integer |
| `policy.job_event_credits` | integer |
| `policy.failed_deliveries_charged` | boolean |
| `policy.max_endpoints` | integer |
| `policy.max_attempts` | integer |
| `policy.disable_after_consecutive_failures` | integer |
| `policy.signature_tolerance_seconds` | integer |

#### 예제 응답

```json
{
  "endpoints": [
    {
      "id": "example-id",
      "url": "https://example.com",
      "events": [
        "example"
      ],
      "status": "active",
      "secret_hint": "example",
      "created_at": "2026-08-27T12:00:00Z",
      "description": "example",
      "disabled_reason": "customer",
      "consecutive_failures": 1,
      "previous_secret_expires_at": "2026-08-27T12:00:00Z",
      "secret_rotated_at": "2026-08-27T12:00:00Z",
      "last_success_at": "2026-08-27T12:00:00Z",
      "last_failure_at": "2026-08-27T12:00:00Z",
      "last_error": "example",
      "updated_at": "2026-08-27T12:00:00Z",
      "disabled_at": "2026-08-27T12:00:00Z"
    }
  ],
  "available_events": [
    "domain.available"
  ],
  "policy": {
    "credits_per_delivered_alert": 2,
    "test_event_credits": 0,
    "failed_deliveries_charged": false,
    "max_endpoints": 1,
    "max_attempts": 1,
    "disable_after_consecutive_failures": 1,
    "signature_tolerance_seconds": 1,
    "secret_rotation_grace_seconds": 86400,
    "secret_rotation_credits": 0,
    "job_event_credits": 0
  },
  "all_events": "*",
  "test_event": "webhook.test"
}
```

PATCH /v1/webhooks/:webhook_id

엔드포인트를 일시 중지하거나 활성화하고, 이벤트를 변경하거나 설명을 수정합니다. 엔드포인트를 활성화하면 실패 횟수가 초기화됩니다.

```json
{ "enabled": true, "events": ["*"] }
```

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `endpoint` | object |
| `endpoint.id` | string |
| `endpoint.url` | string |
| `endpoint.description` | string \| null |
| `endpoint.events[]` | string[] |
| `endpoint.status` | string |
| `endpoint.disabled_reason` | string \| null |
| `endpoint.consecutive_failures` | integer |
| `endpoint.secret_hint` | string |
| `endpoint.previous_secret_expires_at` | string \| null |
| `endpoint.secret_rotated_at` | string \| null |
| `endpoint.last_success_at` | string \| null |
| `endpoint.last_failure_at` | string \| null |
| `endpoint.last_error` | string \| null |
| `endpoint.created_at` | string |
| `endpoint.updated_at` | string |
| `endpoint.disabled_at` | string \| null |

#### 예제 응답

```json
{
  "endpoint": {
    "id": "example-id",
    "url": "https://example.com",
    "events": [
      "example"
    ],
    "status": "active",
    "secret_hint": "example",
    "created_at": "2026-08-27T12:00:00Z",
    "description": "example",
    "disabled_reason": "customer",
    "consecutive_failures": 1,
    "previous_secret_expires_at": "2026-08-27T12:00:00Z",
    "secret_rotated_at": "2026-08-27T12:00:00Z",
    "last_success_at": "2026-08-27T12:00:00Z",
    "last_failure_at": "2026-08-27T12:00:00Z",
    "last_error": "example",
    "updated_at": "2026-08-27T12:00:00Z",
    "disabled_at": "2026-08-27T12:00:00Z"
  }
}
```

DELETE /v1/webhooks/:webhook_id

엔드포인트를 삭제합니다. 대기 중인 전달은 취소되고 예약된 크레딧은 환불됩니다.

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `id` | string |
| `deleted` | boolean |

#### 예제 응답

```json
{
  "id": "example-id",
  "deleted": true
}
```

POST /v1/webhooks/:webhook_id/test

무료 webhook.test 이벤트를 한 번 보내고 결과를 반환합니다. 테스트 이벤트는 재시도되지 않으며 자동 일시 중지 횟수에도 포함되지 않습니다.

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `delivery` | object |
| `delivery.id` | string |
| `delivery.endpoint_id` | string |
| `delivery.event_id` | string |
| `delivery.event_type` | string |
| `delivery.status` | string |
| `delivery.attempts` | integer |
| `delivery.next_attempt_at` | string \| null |
| `delivery.last_attempt_at` | string \| null |
| `delivery.response_status` | integer \| null |
| `delivery.last_error` | string \| null |
| `delivery.credits_charged` | integer |
| `delivery.refunded` | boolean |
| `delivery.created_at` | string |
| `delivery.delivered_at` | string \| null |
| `delivered` | boolean |

#### 예제 응답

```json
{
  "delivery": {
    "id": "example-id",
    "endpoint_id": "example",
    "event_id": "example",
    "event_type": "domain",
    "status": "pending",
    "attempts": 1,
    "next_attempt_at": "2026-08-27T12:00:00Z",
    "last_attempt_at": "2026-08-27T12:00:00Z",
    "response_status": 1,
    "last_error": "example",
    "credits_charged": 1,
    "refunded": false,
    "created_at": "2026-08-27T12:00:00Z",
    "delivered_at": "2026-08-27T12:00:00Z"
  },
  "delivered": false
}
```

POST /v1/webhooks/:webhook_id/rotate-secret

일시 중지된 엔드포인트를 포함해 엔드포인트의 서명 시크릿을 교체합니다. 새 시크릿은 이 응답에서만 반환됩니다. 이후 24시간 동안 모든 전달은 새 시크릿과 이전 시크릿으로 모두 서명됩니다. 교체는 무료입니다.

#### 예제 요청

```bash
curl -X POST "https://domscan.net/v1/webhooks/whe_3f1c0a9b8e7d6c5b4a39281706f5e4d3/rotate-secret" \
  -H "X-API-Key: $DOMSCAN_API_KEY"
```

#### 예제 응답

```json
{
  "endpoint": {
    "id": "whe_3f1c0a9b8e7d6c5b4a39281706f5e4d3",
    "status": "active",
    "secret_hint": "4b7a",
    "previous_secret_expires_at": "2026-10-11T12:00:00.000Z"
  },
  "secret": "whsec_...",
  "previous_secret_expires_at": "2026-10-11T12:00:00.000Z",
  "grace_period_seconds": 86400
}
```

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `endpoint` | object |
| `endpoint.id` | string |
| `endpoint.url` | string |
| `endpoint.description` | string \| null |
| `endpoint.events[]` | string[] |
| `endpoint.status` | string |
| `endpoint.disabled_reason` | string \| null |
| `endpoint.consecutive_failures` | integer |
| `endpoint.secret_hint` | string |
| `endpoint.previous_secret_expires_at` | string \| null |
| `endpoint.secret_rotated_at` | string \| null |
| `endpoint.last_success_at` | string \| null |
| `endpoint.last_failure_at` | string \| null |
| `endpoint.last_error` | string \| null |
| `endpoint.created_at` | string |
| `endpoint.updated_at` | string |
| `endpoint.disabled_at` | string \| null |
| `secret` | string |
| `previous_secret_expires_at` | string |
| `grace_period_seconds` | integer |

GET /v1/webhooks/:webhook_id/deliveries

#### 쿼리 매개변수

| 매개변수 | 유형 | 설명 |
| --- | --- | --- |
| limit | integer | 반환할 전달 내역 수이며 1에서 100까지입니다. 기본값은 50입니다. |

최근 전달 내역을 상태, 시도 횟수, HTTP 응답, 청구된 크레딧, 환불과 함께 나열합니다. 전달 기록은 30일 동안 보관됩니다.

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `endpoint_id` | string |
| `deliveries[]` | object[] |
| `deliveries[]` | object |
| `deliveries[].id` | string |
| `deliveries[].endpoint_id` | string |
| `deliveries[].event_id` | string |
| `deliveries[].event_type` | string |
| `deliveries[].status` | string |
| `deliveries[].attempts` | integer |
| `deliveries[].next_attempt_at` | string \| null |
| `deliveries[].last_attempt_at` | string \| null |
| `deliveries[].response_status` | integer \| null |
| `deliveries[].last_error` | string \| null |
| `deliveries[].credits_charged` | integer |
| `deliveries[].refunded` | boolean |
| `deliveries[].created_at` | string |
| `deliveries[].delivered_at` | string \| null |

#### 예제 응답

```json
{
  "endpoint_id": "example",
  "deliveries": [
    {
      "id": "example-id",
      "endpoint_id": "example",
      "event_id": "example",
      "event_type": "domain",
      "status": "pending",
      "attempts": 1,
      "next_attempt_at": "2026-08-27T12:00:00Z",
      "last_attempt_at": "2026-08-27T12:00:00Z",
      "response_status": 1,
      "last_error": "example",
      "credits_charged": 1,
      "refunded": false,
      "created_at": "2026-08-27T12:00:00Z",
      "delivered_at": "2026-08-27T12:00:00Z"
    }
  ]
}
```

#### 요청 형식

각 알림은 JSON 본문이 있는 POST 요청입니다. 같은 이벤트의 id 필드는 바뀌지 않고 X-DomScan-Event-Id에도 똑같이 들어 있으므로, 이미 처리한 재시도는 무시할 수 있습니다.

```http
POST /domscan HTTP/1.1
Content-Type: application/json
X-DomScan-Event: domain.expiring_30
X-DomScan-Event-Id: evt_6f1d2c3b4a5948e7b6c5d4e3f2a1b0c9
X-DomScan-Timestamp: 1791633600
X-DomScan-Signature: sha256=...

{
  "id": "evt_6f1d2c3b4a5948e7b6c5d4e3f2a1b0c9",
  "type": "domain.expiring_30",
  "created": "2026-10-10T12:00:00.000Z",
  "data": {
    "domain": "acme.io",
    "previous_status": "registered",
    "current_status": "expiring_soon",
    "expiry_date": "2026-11-09T00:00:00Z",
    "days_until_expiry": 30,
    "checked_at": "2026-10-10T12:00:00.000Z"
  }
}
```

#### 서명 검증

엔드포인트 시크릿을 사용해 X-DomScan-Timestamp 값, 마침표, 원본 요청 본문을 차례로 이어 붙인 데이터의 HMAC-SHA256을 계산하세요. 결과를 X-DomScan-Signature와 상수 시간으로 비교하고, 타임스탬프가 5분보다 오래된 요청은 거부하세요.

아래 샘플은 헤더의 모든 서명을 확인하므로 시크릿을 교체하는 중에도 계속 동작합니다.

```javascript
import crypto from 'node:crypto';

// rawBody: the request body exactly as received, before JSON parsing.
function verifyDomScanWebhook(rawBody, headers, secret) {
  const timestamp = headers['x-domscan-timestamp'];
  const header = headers['x-domscan-signature'] || '';
  if (!/^\d{10}$/.test(timestamp || '')) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = Buffer.from('sha256=' + crypto
    .createHmac('sha256', secret)
    .update(timestamp + '.' + rawBody)
    .digest('hex'));
  // One entry normally; two during a secret rotation (new secret first).
  return header.split(',').some((entry) => {
    const candidate = Buffer.from(entry.trim());
    return candidate.length === expected.length &&
      crypto.timingSafeEqual(candidate, expected);
  });
}
```

#### 서명 시크릿 교체

교체 후 24시간의 유예 기간에는 X-DomScan-Signature에 쉼표로 구분된 서명 두 개가 들어 있으며, 아래 예시처럼 새 시크릿의 서명이 먼저 옵니다. 항목 중 하나라도 신뢰하는 시크릿과 일치하면 요청을 받아들이고, 새 시크릿을 배포한 뒤에 이전 시크릿에 대한 신뢰를 중단하세요. 유예 기간 중 다시 교체하면 더 오래된 이전 시크릿은 즉시 폐기됩니다.

```http
X-DomScan-Signature: sha256=<new>,sha256=<previous>
```

#### 재시도와 자동 일시 중지

알림을 수신했음을 알리려면 8초 안에 2xx 상태 코드를 반환하세요. 리디렉션, 그 밖의 상태 코드, 타임아웃은 지수 백오프(1, 4, 16, 64, 256분)로 재시도하며 총 6회까지 시도합니다. 연속 15회 실패하면 엔드포인트가 일시 중지되며, PATCH 또는 모니터 설정에서 다시 활성화할 수 있습니다.

반복된 실패로 엔드포인트가 일시 중지되면 엔드포인트 소유자에게 엔드포인트 호스트, 마지막 오류, 모니터 설정 링크가 담긴 이메일이 한 통 발송됩니다. 엔드포인트를 수정한 뒤 모니터 설정에서, 또는 PATCH로 enabled를 true로 설정해 다시 활성화하세요. 활성화하면 실패 횟수가 0으로 초기화됩니다.

### 관심 목록 알림 구독

관심 목록의 항목에 원하는 알림을 구독하세요. 각 구독은 호스트명, TLD 또는 브랜드 도메인 하나를 모니터링하며, 받을 이벤트를 직접 선택할 수 있습니다. 이벤트는 서명된 웹훅 엔드포인트와 앱 내 알림으로 전달됩니다.

| 유형 | 이벤트 | 발생 시점 |
| --- | --- | --- |
| `ssl` | `ssl.expiring`, `ssl.expired`, `ssl.changed` | ssl.expiring은 호스트명이 제공하는 인증서가 ssl_expiring_days (30, 14, 7 또는 1, 기본값 14) 이내에 만료될 때 인증서와 임계값마다 한 번 발생합니다. ssl.expired는 만료된 인증서마다 한 번 발생합니다. ssl.changed는 새 인증서 핑거프린트가 연속 2회 확인에서 확인될 때 발생하며, 페이로드에 발급자가 변경되었는지 여부가 포함됩니다. |
| `price` | `price.dropped` | TLD의 등록 또는 갱신 가격이 특정 등록기관 또는 최저가 등록기관에서 마지막 알림 이후의 최고 가격보다 drop_percent 이상 떨어지거나 target_price 이하에 도달할 때 발생합니다. 가격은 매일 생성되는 가격 스냅샷에서 가져오며, 각 스냅샷은 한 번만 평가됩니다. |
| `dns` | `dns.changed` | 모니터링 중인 레코드 유형(A, AAAA, MX, NS, TXT 또는 CNAME)이 어떤 호스트명에서든 연속 2회 확인된 조회에서 동일한 새 응답을 반환할 때 발생합니다. |
| `typosquat` | `typosquat.detected` | 브랜드 도메인과 유사한 도메인이 새로 등록되고 공식 등록 조회로 확인될 때 발생합니다. 유사 도메인에 대한 첫 전체 검사는 기준선만 설정하며 알림을 보내지 않습니다. |

정확한 증거가 있을 때만 이벤트가 발생합니다. TLS 핸드셰이크 실패, 실패하거나 시간 초과된 DNS 조회, 누락된 가격, 차단되거나 요청 제한된 조회는 알 수 없음으로 처리되며, 아무것도 변경하지 않고 알림이나 과금도 발생하지 않습니다. 구독의 첫 번째 확인된 검사가 기준선을 설정합니다.

검사는 자동으로 실행됩니다. 인증서는 12시간마다, DNS 레코드는 1시간마다(변경이 처음 감지되고 15분 후 확인 검사 수행), 가격은 매일 생성되는 스냅샷을 기준으로 2시간마다, 유사 도메인은 30개씩 배치로 처리되어 매일 전체를 한 번 검사합니다. 알 수 없음 결과가 나오면 다음 시도가 더 빨리 이루어집니다.

구독 생성과 검사 실행은 무료입니다. 2xx 응답으로 웹훅 엔드포인트에 전달된 알림은 다른 모니터링 알림과 마찬가지로 건당 2 크레딧이 차감됩니다. 전달에 실패한 알림은 환불됩니다. 계정당 최대 100개의 구독을 만들 수 있으며, 이 중 유사 도메인용은 20개까지입니다.

POST /v1/watchlist/alerts

구독을 만듭니다. type에 따라 적용되는 필드가 달라집니다. ssl, dns, typosquat는 domain, price는 tld, registrar, price_type, drop_percent, target_price를 사용합니다. 해당 유형에 맞지 않는 필드는 거부됩니다.

#### 요청 본문

| 매개변수 | 유형 | 설명 |
| --- | --- | --- |
| type | string | ssl, price, dns 또는 typosquat. 필수. |
| domain | string | ssl과 dns에서는 모니터링할 호스트명, typosquat에서는 브랜드 도메인. |
| tld | string | price에서 모니터링할 TLD. 예: com. |
| registrar | string | price용 등록기관 ID. 생략하면 최저가 등록기관을 모니터링합니다. |
| events | string[] | 받을 이벤트. 기본값은 해당 유형의 모든 이벤트입니다. |
| ssl_expiring_days | integer | ssl.expiring의 만료 전 일수: 30, 14, 7 또는 1. 기본값 14. |
| price_type | string | registration(기본값) 또는 renewal. |
| drop_percent | number | 이 비율 이상 하락하면 알림을 보냅니다. 1에서 95 사이. |
| target_price | number | USD 기준 가격이 이 금액 이하에 도달하면 알림을 보냅니다. 가격 구독에는 drop_percent, target_price 또는 둘 다 필요합니다. |
| record_types | string[] | dns에서 모니터링할 레코드 유형. 기본값은 A, AAAA, MX, NS, TXT, CNAME입니다. |

#### 예제 요청

```bash
curl -X POST "https://domscan.net/v1/watchlist/alerts" \
  -H "X-API-Key: $DOMSCAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "price",
    "tld": "com",
    "registrar": "porkbun",
    "price_type": "renewal",
    "drop_percent": 10,
    "target_price": 9.5
  }'
```

#### 예제 응답

```json
{
  "subscription": {
    "id": "mas_8c1f0e2d3b4a59687766554433221100",
    "type": "price",
    "target": "com",
    "registrar": "porkbun",
    "events": ["price.dropped"],
    "status": "active",
    "price_type": "renewal",
    "drop_percent": 10,
    "target_price": 9.5,
    "currency": "USD",
    "observed": null,
    "last_check_status": null,
    "next_check_at": "2026-10-10T12:00:00.000Z"
  }
}
```

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `subscription` | object |
| `subscription.id` | string |
| `subscription.type` | string |
| `subscription.target` | string |
| `subscription.registrar` | string \| null |
| `subscription.events[]` | string[] |
| `subscription.status` | string |
| `subscription.ssl_expiring_days` | integer \| null |
| `subscription.price_type` | string \| null |
| `subscription.drop_percent` | number \| null |
| `subscription.target_price` | number \| null |
| `subscription.currency` | string \| null |
| `subscription.record_types[]` | string[] |
| `subscription.observed` | object \| null |
| `subscription.last_check_at` | string \| null |
| `subscription.last_check_status` | string \| null |
| `subscription.last_error` | string \| null |
| `subscription.next_check_at` | string \| null |
| `subscription.created_at` | string |
| `subscription.updated_at` | string |

GET /v1/watchlist/alerts

구독 목록을 최신 확인 증거와 검사 상태, 유형별 지원 이벤트, 검사 일정, 알림 가격과 함께 가져옵니다.

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `subscriptions[]` | object[] |
| `subscriptions[]` | object |
| `subscriptions[].id` | string |
| `subscriptions[].type` | string |
| `subscriptions[].target` | string |
| `subscriptions[].registrar` | string \| null |
| `subscriptions[].events[]` | string[] |
| `subscriptions[].status` | string |
| `subscriptions[].ssl_expiring_days` | integer \| null |
| `subscriptions[].price_type` | string \| null |
| `subscriptions[].drop_percent` | number \| null |
| `subscriptions[].target_price` | number \| null |
| `subscriptions[].currency` | string \| null |
| `subscriptions[].record_types[]` | string[] |
| `subscriptions[].observed` | object \| null |
| `subscriptions[].last_check_at` | string \| null |
| `subscriptions[].last_check_status` | string \| null |
| `subscriptions[].last_error` | string \| null |
| `subscriptions[].next_check_at` | string \| null |
| `subscriptions[].created_at` | string |
| `subscriptions[].updated_at` | string |
| `total` | integer |
| `available_types` | object |
| `available_types.ssl[]` | string[] |
| `available_types.price[]` | string[] |
| `available_types.dns[]` | string[] |
| `available_types.typosquat[]` | string[] |
| `policy` | object |
| `policy.credits_per_delivered_alert` | integer |
| `policy.check_credits` | integer |
| `policy.unknown_checks_charged` | boolean |
| `policy.max_subscriptions` | integer |
| `policy.max_typosquat_subscriptions` | integer |
| `policy.ssl_expiring_days[]` | integer[] |
| `policy.default_ssl_expiring_days` | integer |
| `policy.record_types[]` | string[] |
| `policy.price_types[]` | string[] |
| `policy.check_interval_minutes` | object |
| `policy.confirmation_checks` | object |

#### 예제 응답

```json
{
  "subscriptions": [
    {
      "id": "example-id",
      "type": "ssl",
      "target": "example",
      "registrar": "example",
      "events": [
        "ssl.expiring"
      ],
      "status": "active",
      "ssl_expiring_days": 30,
      "price_type": "registration",
      "drop_percent": 0.5,
      "target_price": 0.5,
      "currency": "USD",
      "record_types": [
        "A"
      ],
      "observed": {},
      "last_check_at": "2026-08-27T12:00:00Z",
      "last_check_status": "baseline",
      "last_error": "example",
      "next_check_at": "2026-08-27T12:00:00Z",
      "created_at": "2026-08-27T12:00:00Z",
      "updated_at": "2026-08-27T12:00:00Z"
    }
  ],
  "total": 1,
  "available_types": {
    "ssl": [
      "ssl.expiring"
    ],
    "price": [
      "price.dropped"
    ],
    "dns": [
      "dns.changed"
    ],
    "typosquat": [
      "typosquat.detected"
    ]
  },
  "policy": {
    "credits_per_delivered_alert": 2,
    "check_credits": 0,
    "unknown_checks_charged": false,
    "max_subscriptions": 1,
    "check_interval_minutes": {},
    "max_typosquat_subscriptions": 1,
    "ssl_expiring_days": [
      1
    ],
    "default_ssl_expiring_days": 1,
    "record_types": [
      "A"
    ],
    "price_types": [
      "registration"
    ],
    "confirmation_checks": {}
  }
}
```

GET /v1/watchlist/alerts/:subscription_id

구독 하나를 최신 확인 증거와 가장 최근 이벤트 20개와 함께 가져옵니다.

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `subscription` | object |
| `subscription.id` | string |
| `subscription.type` | string |
| `subscription.target` | string |
| `subscription.registrar` | string \| null |
| `subscription.events[]` | string[] |
| `subscription.status` | string |
| `subscription.ssl_expiring_days` | integer \| null |
| `subscription.price_type` | string \| null |
| `subscription.drop_percent` | number \| null |
| `subscription.target_price` | number \| null |
| `subscription.currency` | string \| null |
| `subscription.record_types[]` | string[] |
| `subscription.observed` | object \| null |
| `subscription.last_check_at` | string \| null |
| `subscription.last_check_status` | string \| null |
| `subscription.last_error` | string \| null |
| `subscription.next_check_at` | string \| null |
| `subscription.created_at` | string |
| `subscription.updated_at` | string |
| `events[]` | object[] |
| `events[]` | object |
| `events[].id` | string |
| `events[].event_type` | string |
| `events[].detected_at` | string |
| `events[].published_at` | string \| null |
| `events[].data` | object |

#### 예제 응답

```json
{
  "subscription": {
    "id": "example-id",
    "type": "ssl",
    "target": "example",
    "registrar": "example",
    "events": [
      "ssl.expiring"
    ],
    "status": "active",
    "ssl_expiring_days": 30,
    "price_type": "registration",
    "drop_percent": 0.5,
    "target_price": 0.5,
    "currency": "USD",
    "record_types": [
      "A"
    ],
    "observed": {},
    "last_check_at": "2026-08-27T12:00:00Z",
    "last_check_status": "baseline",
    "last_error": "example",
    "next_check_at": "2026-08-27T12:00:00Z",
    "created_at": "2026-08-27T12:00:00Z",
    "updated_at": "2026-08-27T12:00:00Z"
  },
  "events": [
    {
      "id": "example-id",
      "event_type": "ssl.expiring",
      "detected_at": "2026-08-27T12:00:00Z",
      "published_at": "2026-08-27T12:00:00Z",
      "data": {}
    }
  ]
}
```

PATCH /v1/watchlist/alerts/:subscription_id

enabled로 구독을 일시 중지하거나 다시 시작하고, events, ssl_expiring_days, drop_percent, target_price, record_types를 변경합니다. 새 목표 가격은 이후의 가격 변동에 대해서만 알림을 보냅니다.

```json
{ "enabled": false }
```

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `subscription` | object |
| `subscription.id` | string |
| `subscription.type` | string |
| `subscription.target` | string |
| `subscription.registrar` | string \| null |
| `subscription.events[]` | string[] |
| `subscription.status` | string |
| `subscription.ssl_expiring_days` | integer \| null |
| `subscription.price_type` | string \| null |
| `subscription.drop_percent` | number \| null |
| `subscription.target_price` | number \| null |
| `subscription.currency` | string \| null |
| `subscription.record_types[]` | string[] |
| `subscription.observed` | object \| null |
| `subscription.last_check_at` | string \| null |
| `subscription.last_check_status` | string \| null |
| `subscription.last_error` | string \| null |
| `subscription.next_check_at` | string \| null |
| `subscription.created_at` | string |
| `subscription.updated_at` | string |

#### 예제 응답

```json
{
  "subscription": {
    "id": "example-id",
    "type": "ssl",
    "target": "example",
    "registrar": "example",
    "events": [
      "ssl.expiring"
    ],
    "status": "active",
    "ssl_expiring_days": 30,
    "price_type": "registration",
    "drop_percent": 0.5,
    "target_price": 0.5,
    "currency": "USD",
    "record_types": [
      "A"
    ],
    "observed": {},
    "last_check_at": "2026-08-27T12:00:00Z",
    "last_check_status": "baseline",
    "last_error": "example",
    "next_check_at": "2026-08-27T12:00:00Z",
    "created_at": "2026-08-27T12:00:00Z",
    "updated_at": "2026-08-27T12:00:00Z"
  }
}
```

DELETE /v1/watchlist/alerts/:subscription_id

구독을 삭제합니다. 아직 전송되지 않은 이벤트는 폐기되며 과금되지 않습니다.

#### 응답 필드

| 필드 | 유형 |
| --- | --- |
| `id` | string |
| `deleted` | boolean |

#### 예제 응답

```json
{
  "id": "example-id",
  "deleted": true
}
```

#### dns.changed 이벤트 예시

```json
{
  "id": "evt_2b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e",
  "type": "dns.changed",
  "created": "2026-10-10T12:15:00.000Z",
  "data": {
    "domain": "partner-shop.example",
    "changes": [
      {
        "record_type": "MX",
        "before": ["10 mx1.mail.example"],
        "after": ["10 mx.other-mail.example"],
        "added": ["10 mx.other-mail.example"],
        "removed": ["10 mx1.mail.example"],
        "first_seen_at": "2026-10-10T12:00:00.000Z"
      }
    ],
    "confirmed_checks": 2,
    "confirmed_at": "2026-10-10T12:15:00.000Z",
    "subscription_id": "mas_8c1f0e2d3b4a59687766554433221100"
  }
}
```
