전송 동작

표준 페이로드, 5초 제한 시간, 재시도가 없는 이유와 각 전송 상태의 의미를 설명합니다.

웹훅 전달 방식은 의도적으로 단순하게 설계되어 있으며, 이 단순한 방식은 수신 측 구현에 영향을 줍니다. HTTP 요청은 1번만 시도하며, 제한 시간은 5초이고, 재시도는 없습니다. 핸들러가 요청 처리 중에 동기적으로 수행할 작업의 범위를 정하기 전에 이 페이지를 읽어 보시기 바랍니다.

표준 페이로드

listing.submission을 제외한 모든 이벤트는 동일한 최상위 구조로 전달되며, 이벤트별 필드는 data 아래에 중첩됩니다.

JSON
{  "event": "connection.location_connected",  "timestamp": "2026-08-25T14:32:10Z",  "account_id": 11073,  "location_id": "279381",  "data": {    "platform": "google",    "connected_account_id": "ba03bc4b-9f8c-4d3b-8e93-9628034c63cc"  }}
필드유형참고
eventstring점으로 구분된 이벤트 이름입니다. 이 값을 기준으로 분기합니다.
timestampstringISO-8601 UTC 형식이며, 실제 변경이 발생한 시점이 아니라 전달 요청을 생성한 시점에 기록됩니다.
account_idinteger이벤트가 속한 계정입니다.
location_idstring숫자 문자열이며, 279381이 아니라 "279381"입니다. interaction.*에서는 형식을 강제 변환하지 않습니다. 아래 예외를 참조하시기 바랍니다.
dataobject이벤트별 필드입니다. 이벤트 참조에 각 이벤트별로 설명되어 있습니다.
agency_account_idinteger계정을 상위 대행사 계정에서 관리하는 경우에만 존재합니다.

data 내의 선택적 필드는 값이 비어 있으면 null로 전송되지 않고 생략됩니다. 이러한 경우를 고려하여 코드를 작성해야 합니다. 누락된 키와 명시적인 null를 동일하게 처리하고, 선택 사항으로 문서화된 키가 항상 존재한다고 가정하지 않아야 합니다.

2가지 예외

5초 제한 시간

각 전달은 단일 POST 요청이며, 연결 제한 시간은 5초이고 읽기 제한 시간도 5초입니다. 엔드포인트가 시간 내에 응답하지 않으면 시도를 중단하고 timeout로 기록합니다.

이 제한 시간은 핸들러의 전체 처리에 적용됩니다. 안전한 설계 방식은 다음뿐입니다.

  1. 서명을 검증합니다.
  2. 큐, 테이블, 로그 등 영구 저장이 가능한 곳에 페이로드를 기록합니다.
  3. 200을 반환합니다.

그 밖의 모든 작업(데이터 보강, 여러 대상으로 작업 분배, REST API 재호출, 타사 서비스와 연동하는 모든 작업)은 큐에서 꺼낸 뒤에 처리해야 합니다.

재시도는 없습니다

전달은 정확히 1번만 시도합니다. 백오프, 재시도 큐, 데드 레터 큐는 없습니다. 엔드포인트가 2분 동안 중단되면 그 2분 동안 발생한 이벤트는 유실됩니다. 해당 이벤트는 나중에도 전달되지 않습니다.

이에 따른 다음 2가지 결과를 명확히 이해해야 합니다.

  • 리디렉션을 따르지 않습니다. 리디렉션은 완전히 비활성화되어 있으므로 엔드포인트가 301 또는 302을 반환하면 다른 URL로 이동하지 않고 전달 실패로 기록합니다. 웹훅 URL을 최종 URL로 설정해야 합니다. 많은 웹 서버가 기본적으로 추가하는 기본 도메인에서 www로의 리디렉션과 후행 슬래시 리디렉션에 유의해야 합니다.
  • 2xx만 전달 성공으로 간주합니다. 3xx, 4xx, 5xx은 모두 실패로 기록됩니다.

중복 및 순서

  • 같은 이벤트를 2번 수신할 수 있으며, 서로 다른 2개의 변경에 같은 id가 사용될 수 있습니다. data에 포함된 식별자(interaction id, post item id, connected-account id)는 변경이 아니라 리소스를 식별합니다. interaction.review는 리뷰가 변경될 때마다 같은 interaction id로 다시 발생하며, 어떤 페이로드에도 버전이나 updated-at 필드가 없으므로 id + event를 기준으로 영구적으로 중복을 차단하는 핸들러는 실제 수정 사항까지 알림 없이 버립니다. 대신 완전히 동일한 중복을 감지합니다. 원시 본문 바이트의 해시를 계산하고, 짧고 제한된 시간 구간(영구적이 아닌 몇 분 단위) 안에서 같은 해시가 반복되면 무시합니다. 같은 id라도 본문이 다르면 중복이 아니라 새로운 변경입니다. 그런 다음 REST API에서 리소스를 다시 조회하고 그 조회 결과를 기준으로 삼습니다. 쓰기를 건너뛰지 말고 리소스 id를 키로 사용해 저장합니다.
  • 순서는 보장되지 않으며, timestamp로도 이를 해결할 수 없습니다. 이벤트는 여러 독립적인 경로에서 생성되어 도착하는 대로 전달되므로, 먼저 올 것으로 예상한 local_post.created보다 local_post.published가 먼저 도착할 수 있습니다. 같은 리소스에 대한 2개의 변경을 timestamp 기준으로 정렬해서는 안 됩니다. 이 값은 변경이 발생한 시점이 아니라 전달할 데이터를 구성하는 시점에 기록되며, 페이로드 어디에도 버전이나 시퀀스 번호가 없습니다. 순서가 중요하다면 리소스를 다시 조회하고 API가 반환하는 값을 사용합니다.
  • profile.updated에는 디바운스가 적용되어 사업장별로 60초마다 1번만 전달되며, 시간 구간의 시작 시점에 전달됩니다. 이는 전달을 지연하는 것이 아니라 실제로 여러 수정을 하나로 줄이는 방식입니다. 첫 번째 수정이 시간 구간을 시작하면서 전달되고, 해당 구간 내의 모든 수정은 버려져 전달되지 않으며, changed_fields에는 시간 구간을 시작한 수정만 기술됩니다. 따라서 이벤트는 시간 구간의 시작에 도착하므로, 이벤트 도착을 계기로 다시 조회하면 첫 번째 수정만 확인할 수 있습니다. 이벤트 발생 후 최소 60초가 지난 시점인 시간 구간 종료 후에 두 번째 재조회를 예약하고, 타이머에 따라 사업장 데이터도 대조하여 동기화합니다. 알림 자체가 유실될 수도 있으며, 전달은 1번만 시도하고 재시도하지 않기 때문입니다. 그 외에는 디바운스가 적용되지 않습니다.

누락된 이벤트 복구

재시도와 재전송이 없으므로 모든 이벤트 유형에 대해 대체 수단으로 사용할 수 있는 신뢰할 수 있는 조회 방법이 필요합니다. 문제가 있어 보일 때만이 아니라 정해진 일정에 따라 데이터를 대조하여 동기화합니다.

이벤트 유형재조회 방법권장 주기조회로 복구할 수 없는 정보
listing.submissionGET /locations/{locationId}/listings/premium를 listingType=premium로 1번, additional로 1번 조회합니다. 이벤트에서 알리는 사이트별 syncStatus 및 listingUrl입니다.사업장별로 매일, 그리고 사업장 일괄 수정 후에 조회합니다.레거시 페이로드에는 timestamp도 account_id도 없으므로, 누락된 이벤트가 언제 발생했는지 또는 어느 계정에 속하는지 확인할 수 없습니다. incomplete 제출의 error_message는 어떤 조회에서도 제공되지 않습니다.
profile.*GET /locations, GET /locations/search 또는 GET /locations-by-ids입니다. 각 사업장의 updatedAt를 통해 어떤 사업장이 변경되었는지 확인할 수 있습니다.매시간 조회하고, 위에서 설명한 시간 구간 종료 후 재조회도 수행합니다.source(user / api / bulk_edit) 및 changed_fields입니다. 조회에는 현재 상태만 나타나며, 누가 무엇을 변경했는지는 전혀 나타나지 않습니다. 60초 디바운스로 인해 생략된 수정은 흔적이 전혀 남지 않으며, 삭제된 사업장의 삭제 전 상태도 사라집니다.
connection.*연결, 연결 해제 및 인증 상태는 GET /connected-accounts 및 GET /connected-accounts/{connectedAccountId}/details로 확인합니다. Google 인증 이벤트 쌍의 경우, 사업장을 조회하는 어떤 방법으로든 조회 결과에서 googleVerificationStatus { status, message }를 확인합니다.매시간 조회하며, 인증이 진행 중일 때는 몇 분마다 조회합니다.failure_type(rejected 또는 transient) 및 reason 문자열입니다. 연결 상태의 반복적인 변동은 나타나지 않습니다. 연결이 끊겼다가 복구된 경우 조회 결과에는 연결된 상태로 표시됩니다.
interaction.*사업장별로는 GET /locations/{locationId}/reviews, 여러 사업장에 걸쳐서는 GET /rollup_interactions, id별로는 GET /reviewDetails를 사용합니다.새 리뷰는 15분마다, 더 넓은 범위는 매일 조회합니다.변경이 있었다는 사실입니다. 문서에 명시된 interaction 필드에는 updated-at이 없으므로, 수정된 리뷰도 이미 저장한 리뷰와 똑같이 보입니다. 저장하는 필드를 비교합니다.
local_post.*현재 사이트별 게시 상태는 GET /locations/{locationId}/posts, GET /locations/{locationId}/bulk-posts, GET /posts/{postId}로 확인합니다.게시물 처리가 진행 중일 때 매시간 조회합니다.전달 당시의 rejection_reason와 게시물 항목이 거쳐 온 상태의 순서입니다. 조회 결과에는 상태 전환 이력이 아니라 현재 상태가 제공됩니다.
review_analytics.*해당 기간의 startDate 및 endDate를 지정하여 GET /locations/{locationId}/review-analytics-overview를 조회합니다.집계 시간 구간이 지난 후 매일 조회하며, 주간 이벤트 유형은 매주 조회합니다.해당 시점의 수치입니다. 개요는 조회 시점에 다시 계산되므로, 늦게 도착하거나 삭제된 리뷰가 있으면 스냅샷에 담긴 수치와 달라집니다.

전송 결과

외부로 요청을 보내지 않은 시도를 포함하여 모든 시도는 상태와 함께 기록됩니다. 아래는 실제로 기록되는 정확한 문자열입니다.

4가지 결과와 각 결과를 기록하는 상태 문자열입니다. '차단됨'과 '요청 한도 초과'는 HTTP 요청이 전혀 이루어지지 않았음을 의미하므로 실제 실패와 구분해서 기록할 필요가 있습니다.
상태결과의미조치
success전송 완료엔드포인트가 2xx를 반환했습니다.조치가 필요하지 않습니다.
문자열 형식의 HTTP 코드, e.g. "404", "500"실패엔드포인트가 2xx가 아닌 해당 코드로 응답했습니다. 리디렉션을 따르지 않으므로 3xx도 여기에 해당합니다.경로, 리디렉션 체인, 핸들러의 오류율을 확인합니다.
999실패응답을 받았으나 유효한 상태 코드가 없었습니다.일반적으로 엔드포인트 앞단의 프록시나 로드 밸런서가 원인입니다.
timeout실패제한 시간인 5초 이내에 응답이 없었습니다.작업을 요청 처리 경로에서 분리하고 수신 확인 응답을 더 빨리 반환합니다.
network_error실패호스트의 주소를 확인하거나 호스트에 연결할 수 없었습니다.DNS와 외부에서 호스트의 주소를 확인할 수 있는지 확인합니다.
error실패시도 중 TLS 핸드셰이크 실패와 같이 위 항목에 해당하지 않는 오류가 발생했습니다.인증서 체인과 서버 로그를 확인합니다.
blocked_url차단됨연결 시점에 URL이 안전성 검사를 통과하지 못했습니다. HTTPS가 아니거나, 호스트가 없거나, 사설 주소, 루프백 주소, 링크 로컬 주소 또는 메타데이터 주소로 확인되었습니다.URL이 공개 HTTPS 호스트를 가리키도록 설정합니다. 전송할 때마다 이 검사를 다시 수행하므로 DNS가 변경되면 이전에 작동하던 URL도 차단될 수 있습니다.
endpoint_unverified차단됨계정에 서명용 비밀 키가 있지만 엔드포인트가 검증을 통과하지 않았거나 검증이 초기화되었습니다.대시보드에서 엔드포인트 검증을 다시 실행합니다.
rate_limited요청 빈도 제한 초과분당 또는 일일 한도를 초과했습니다. 이벤트가 삭제되었습니다.전송량을 줄이거나 한도가 더 높은 요금제로 변경합니다. 요금제 및 한도를 참조합니다.

수신 측 점검 목록

  • 모든 요청의 서명을 검증하고, 일치하지 않으면 401를 반환하며 아무것도 처리하지 않습니다.
  • 원본 본문 바이트를 해시하며, 다시 직렬화한 객체는 절대 해시하지 않습니다.
  • 5초보다 충분히 짧은 시간 안에 2xx를 반환하고 작업은 큐에 저장합니다.
  • 반복 처리해도 같은 결과가 나오도록 data에 포함된 ID를 각 핸들러 쓰기 작업의 키로 사용하되, 반복된 id를 처리할 작업이 없다는 의미로 간주해서는 안 됩니다.
  • 도착 순서로 순서를 추론하지 않으며, timestamp를 기준으로 정렬하지 않습니다.
  • 수신 시 location_id를 문자열로 정규화합니다.
  • 인식할 수 없는 event 값은 무시하되, 200는 반환합니다.
  • 웹훅 URL은 리디렉션 없이 최종 URL로 제공합니다.
  • REST API와 주기적으로 대조하여 불일치를 해소합니다. 재시도로 복구할 수는 없습니다.