/v1/account/webhook-endpoints

account/webhook-endpoints

웹훅: Monitoring이 새 Signals를 보내는 곳과 이를 검증하는 방법.

비용과 한도

비용과 한도
항목 크레딧
웹훅 엔드포인트와 전달 관리 무료

한도: 분당 30 요청. 생성, 수정, 삭제: 분당 10. 확인, 테스트, 새 시크릿: 분당 6.

관리자 토큰 또는 API 키

엔드포인트

메서드
메서드 경로 용도 접근
GET /v1/account/webhook-endpoints 보관된 것을 제외한 모든 웹훅 엔드포인트. API 키 또는 관리자 토큰
POST /v1/account/webhook-endpoints 웹훅 엔드포인트 생성. 관리자 토큰
POST /v1/account/webhook-endpoints/{id}/verify 확인. API 키 또는 관리자 토큰
POST /v1/account/webhook-endpoints/{id}/test 테스트 메시지 보내기. API 키 또는 관리자 토큰
POST /v1/account/webhook-endpoints/{id}/rotate-secret 새 서명 시크릿. 관리자 토큰
POST /v1/account/webhook-endpoints/{id}/enable 활성화. 관리자 토큰
POST /v1/account/webhook-endpoints/{id}/disable 비활성화. 관리자 토큰
DELETE /v1/account/webhook-endpoints/{id} 보관. 관리자 토큰
GET /v1/account/webhook-deliveries 최근 50건의 전달. API 키 또는 관리자 토큰
POST /v1/account/webhook-deliveries/{id}/retry 실패한 전달 다시 보내기. API 키 또는 관리자 토큰
GET /v1/account/webhook-events 최근 50건의 이벤트. API 키 또는 관리자 토큰

웹훅 엔드포인트 생성

curl -X POST "https://handelsregister.ai/api/v1/account/webhook-endpoints" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "CRM-Sync",
    "url": "https://hooks.example.com/handelsregister",
    "headers": {
        "X-Receiver-Token": "YOUR_RECEIVER_TOKEN"
    }
}'
파라미터
이름 타입 설명 예시
Idempotency-KeyHeader 문자열필수 작업마다 고유한 값, UUID가 가장 좋아요. Idempotency-Key를 참고하세요.제한: 1~128자 req-20260802-01
nameBody 문자열필수 내가 알아보기 위한 이름.제한: 1~120자 CRM-Sync
urlBody 문자열 (uri)필수 내 서버의 주소: 공개적으로 접근 가능하고, 포트 443의 https이며, IP 주소 대신 호스트 이름.제한: 1~2,048자 https://hooks.example.com/handelsregister
headersBody 객체선택 모든 메시지와 함께 보내는 사용자 지정 헤더, 예: 내 서버용 토큰.제한: 최대 10개 항목 {"X-Receiver-Token":"YOUR_RECEIVER_TOKEN"}
1{2  "endpoint": {3    "id": "wep_01m6b3j1g0cdz98hw8zdefbf52",4    "name": "CRM-Sync",5    "url": "hooks.example.com/…",6    "status": "pending_verification",7    "verified_at": null,8    "header_names": [9      "X-Receiver-Token"10    ],11    "consecutive_failures": 0,12    "created_at": "2026-11-01T08:28:01+00:00"13  },14  "signing_secret": "whsec_…",15  "meta": {16    "message": "Endpoint created. Store the signing secret now — it is shown only once.",17    "request_credit_cost": 0,18    "credits_remaining": 14519  }20}
201 · 예시 응답: 새 웹훅 엔드포인트, 여기서는 시크릿을 줄여 표시

확인

1{2  "id": "msg_01m6b3jsqq7ntwzkdj099ayraz",3  "type": "endpoint.verification",4  "timestamp": "2026-11-01T08:28:26.743Z",5  "schema_version": 1,6  "data": {7    "challenge": "189fb34b2af819bb13cb47d9806dbf30"8  }9}
확인할 때 서버가 받는 메시지
curl -X POST "https://handelsregister.ai/api/v1/account/webhook-endpoints/wep_01m6b3j1g0cdz98hw8zdefbf52/verify" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
1{2  "verified": true,3  "endpoint": {4    "id": "wep_01m6b3j1g0cdz98hw8zdefbf52",5    "name": "CRM-Sync",6    "url": "hooks.example.com/…",7    "status": "active",8    "verified_at": "2026-11-01T08:28:26+00:00",9    "header_names": [10      "X-Receiver-Token"11    ],12    "consecutive_failures": 0,13    "created_at": "2026-11-01T08:28:01+00:00"14  },15  "meta": {16    "request_credit_cost": 0,17    "credits_remaining": 14518  }19}
200 · 확인에 성공했을 때의 예시 응답
1{2  "delivery": {3    "id": "del_01m6b3jsyrnc3sz1mt31dq6107",4    "message_id": "msg_01m6b3jsynxzhvagm7n2nq7mpv",5    "event_id": "evt_01m6b3jsynxzhvagm7n2nq7mpt",6    "event_type": "endpoint.test",7    "endpoint_id": "wep_01m6b3j1g0cdz98hw8zdefbf52",8    "status": "succeeded",9    "attempts": 1,10    "last_status_code": 200,11    "last_error_category": null,12    "next_attempt_at": "2026-11-01T08:28:26+00:00",13    "succeeded_at": "2026-11-01T08:28:26+00:00",14    "created_at": "2026-11-01T08:28:26+00:00"15  },16  "meta": {17    "request_credit_cost": 0,18    "credits_remaining": 14519  }20}
200 · 테스트 메시지에 대한 예시 응답

서명 검증

헤더
헤더 의미
webhook-id 메시지 ID(msg_…). 재시도에서도 같아요. 중복을 알아보는 데 쓰세요.
webhook-timestamp 이 전달 시도의 시각(Unix 초).
webhook-signature v1,<Base64> 형태의 서명 하나 이상, 공백으로 구분.
import base64, hashlib, hmac, time

def verify_webhook(secret: str, headers: dict, body: bytes) -> bool:
    msg_id = headers["webhook-id"]
    timestamp = headers["webhook-timestamp"]
    # 5분보다 오래된 메시지는 거부
    if abs(time.time() - int(timestamp)) > 300:
        return False
    key = base64.b64decode(secret.removeprefix("whsec_"))
    signed = f"{msg_id}.{timestamp}.".encode() + body
    expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
    # 시크릿 교체 중에는 헤더에 서명이 두 개 있어요
    for part in headers["webhook-signature"].split(" "):
        version, _, signature = part.partition(",")
        if version == "v1" and hmac.compare_digest(signature, expected):
            return True
    return False

페이로드

필드
필드 의미
id 메시지 ID, 헤더 webhook-id와 같아요.
event_id 이벤트 ID(evt_…).
type 이벤트 유형, 아래 참고.
timestamp 이벤트를 감지한 시각(UTC).
schema_version 구조의 버전, 현재 1.
data 이벤트 데이터.
이벤트 유형
이벤트 유형 의미
organization.signal.detected 모니터가 새 Signal을 찾았어요. data.signal은 signals의 Signal과 같은 구조이고, data.monitor와 data.links.signal 아래 API의 Signal 링크가 더해져요.
organization.signal.sample 계정 › Monitoring에서 보낸, 회사의 실제 이력에서 가져온 예시. 위와 같은 구조에 data.sample: true가 더해져요.
endpoint.test /test로 보낸 테스트 메시지.
endpoint.verification 확인할 때의 챌린지.
1{2  "id": "msg_01m65pqg9x0176cteqvtxw68sa",3  "event_id": "evt_01m65pqg9sx7mwmw0edc1mfbbe",4  "type": "organization.signal.sample",5  "timestamp": "2026-10-30T06:07:37.000Z",6  "schema_version": 1,7  "data": {8    "monitor": {9      "id": "mon_01m65pn3vm11sjjp1j076cbvec",10      "type": "organization.signals"11    },12    "sample": true,13    "signal": {14      "event": {15        "id": "36c7c8fddf99bb0299d6815bcaa01013",16        "topic": "TRANSFORMATIONS",17        "topic_name": {18          "de": "Umwandlungen & M&A",19          "en": "Transformations & M&A"20        },21        "occurred_on": "2022-12-22",22        "announced_on": "2023-08-10",23        "date_basis": "SOURCE_EVENT"24      },25      "organization": {26        "type": "ORGANIZATION",27        "entity_id": "94eb910fef13f94585c48c263fa446d4",28        "current_profile": {29          "name": "Hetzner Online GmbH",30          "registration": {31            "register_type": "HRB",32            "register_number": "6089",33            "court": "Ansbach",34            "register_locality": "Ansbach",35            "registration_authority_id": "D3201",36            "register_entity_name": "Ansbach HRB 6089"37          },38          "legal_form": {39            "name": {40              "de": "Gesellschaft mit beschränkter Haftung"41            },42            "short": {43              "de": "GmbH"44            }45          },46          "seat": {47            "city": "Gunzenhausen"48          },49          "status": {50            "code": "ACTIVE"51          }52        }53      },54      "parties": {55        "counterparty": {56          "entity": {57            "type": "ORGANIZATION",58            "entity_id": "dd4d2d6854b2dbd1ffce74e669f3852e",59            "current_profile": {60              "name": "Headtrip GmbH",61              "registration": {62                "register_type": "HRB",63                "register_number": "33432",64                "court": "Nürnberg",65                "register_locality": "Nuremberg",66                "registration_authority_id": "D3310",67                "register_entity_name": "Nürnberg HRB 33432"68              },69              "legal_form": {70                "name": {71                  "de": "Gesellschaft mit beschränkter Haftung"72                },73                "short": {74                  "de": "GmbH"75                }76              },77              "seat": {78                "city": "Nürnberg"79              },80              "status": {81                "code": "INACTIVE"82              }83            }84          },85          "relationship": "TRANSFERRING_COMPANY"86        }87      },88      "register_entry": {89        "entry_number": "3",90        "entry_date": "2023-08-10",91        "description_text": "Verschmelzung zur Aufnahme der Headtrip GmbH",92        "context": "PRIMARY",93        "flags": []94      },95      "source": {96        "kind": "COMMERCIAL_REGISTER"97      },98      "details": {99        "schema": "TRANSFORMATION",100        "kind": "MERGER",101        "specifics": {102          "merger_target": "ABSORPTION"103        },104        "direction": "INCOMING",105        "is_cross_border": false,106        "resolution_date": "2022-12-22",107        "agreement_date": "2022-12-22"108      }109    },110    "links": {111      "signal": "https://handelsregister.ai/api/v1/signals/36c7c8fddf99bb0299d6815bcaa01013"112    }113  }114}
Hetzner Online GmbH, Ansbach HRB 6089에 대한 예시 메시지: 이력에서 예시로 보낸 Headtrip GmbH, Nürnberg HRB 33432의 합병

전달과 재시도

모든 메시지를 최소 한 번 전달하지만, 순서는 정해져 있지 않아요. webhook-id로 중복을 알아보세요. 빨리 응답하고, 메시지 처리는 그 뒤에 하세요.
전달 상태
상태 의미
withheld 확인이 과금될 때까지 기다려요.
pending 전송을 기다려요.
in_flight 지금 전송 중이에요.
retry_wait 다음 시도를 기다려요.
succeeded 전달됐어요.
exhausted 모든 시도가 실패했어요.
blocked_endpoint 웹훅 엔드포인트가 지금은 아무것도 받을 수 없어요.
cancelled 취소됐어요. 예: 메시지가 만료된 경우.

활성화, 비활성화, 보관

curl -X POST "https://handelsregister.ai/api/v1/account/webhook-endpoints/wep_01m6b3j1g0cdz98hw8zdefbf52/disable" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"

오류

오류
상태 원인 해결 방법
400 URL에 api_key가 있어요. 키를 헤더 x-api-key로 보내세요.
400 URL을 대상으로 쓸 수 없거나, 헤더가 허용되지 않거나, 최대 개수에 도달했거나, 웹훅 엔드포인트가 활성 상태가 아니에요. meta.message에 이유가 있어요. 값을 고치거나 먼저 확인을 마치세요.
400 Idempotency-Key의 형식이 유효하지 않아요. 형식을 고치세요. 예: UUID를 쓰세요.
401 API 키나 토큰이 없거나 유효하지 않아요. 키를 헤더 x-api-key로 보내세요.
403 Bearer 토큰에 account:read 권한이 없어요. API 키나 account:read가 있는 토큰을 쓰세요.
403 계정의 이메일 주소가 아직 인증되지 않았거나 계정이 잠겼어요. 인증 이메일의 링크를 여세요. 계정이 잠겼다면 지원팀이 도와드려요.
403 이 호출에는 관리자 토큰이 필요해요. API 키로도, 다른 토큰으로도 안 돼요. Authorization: Bearer와 관리자 토큰으로 보내세요.
403 Bearer 토큰에 monitoring:manage 권한이 없어요. API 키나 관리자 토큰을 쓰세요.
404 이 ID의 웹훅 엔드포인트나 전달이 없어요. ID를 확인하세요.
409 Idempotency-Key가 이미 다른 데이터로 쓰였거나, 24시간보다 오래되었거나, 아직 처리 중이에요. 새 키를 쓰거나 잠시 기다리세요.
422 name이나 url 같은 값이 없거나 유효하지 않아요. errors에 필드가 나와요. 값을 고치고 새 키로 다시 시도하세요.
422 확인에 실패했어요. 챌린지 헤더와 2xx 상태를 확인한 뒤 새 키로 다시 시도하세요.
428 헤더 Idempotency-Key가 없어요. 헤더를 보내세요.
429 요청이 너무 많아요. Retry-After 이후에 다시 시도하세요.
503 확인, 테스트, 재전송을 일시적으로 쓸 수 없어요. 조금 뒤에 다시 시도하세요.
1{2  "error": "invalid_request",3  "meta": {4    "message": "The endpoint is not active.",5    "request_credit_cost": 0,6    "credits_remaining": 1457  }8}
400 · 아직 확인되지 않은 웹훅 엔드포인트는 테스트 메시지를 받을 수 없어요