본문으로 건너뛰기

Outbound 웹훅 관리

PAT로 Outbound 웹훅(ZeroTalk → 내 서버)을 직접 등록하고 관리하는 엔드포인트입니다. 대시보드 없이 BFF에서 웹훅을 프로비저닝하고 상태를 모니터링할 수 있습니다.

Inbound vs Outbound
  • Outbound 웹훅 (이 페이지) — ZeroTalk가 내 서버로 이벤트를 보냅니다. 페이로드·서명·전달 규약은 웹훅 문서를 참고하세요.
  • Inbound 웹훅 설정(inbound-webhook-configs) — 외부 메신저 공급사(kakao·naver 등)가 ZeroTalk로 메시지를 전달하는 수신 설정입니다. 조회 전용이며 인바운드 웹훅 설정에 있습니다.

Base: /api/public/v1/outbound-webhooks · 인증: Authorization: Bearer ztpat_… · Scope: 조회는 webhooks:read, 변경은 webhooks:write.

엔드포인트 요약​

Method · PathScope용도
GET /outbound-webhookswebhooks:read목록 (시크릿 미반환)
POST /outbound-webhookswebhooks:write생성 (평문 secret 1회 반환)
GET /outbound-webhooks/{id}webhooks:read단건 조회 (status·failure_count·last_delivered_at)
PATCH /outbound-webhooks/{id}webhooks:writename·url·event_filters·match_filters 수정 (부분)
DELETE /outbound-webhooks/{id}webhooks:write삭제
POST /outbound-webhooks/{id}/regenerate-secretwebhooks:write시크릿 회전 (평문 1회 반환)
POST /outbound-webhooks/{id}/togglewebhooks:write활성/비활성 전환 (error 상태에서 재활성하면 failure_count 리셋)
GET /outbound-webhooks/{id}/delivery-logswebhooks:read최근 전달 시도
시크릿은 1회만 노출

서명 secret은 생성과 시크릿 회전 응답에서만 평문으로 반환됩니다. 목록·단건 조회는 시크릿을 절대 반환하지 않습니다. 받은 즉시 서버에 저장하세요.

URL 제약​

  • HTTPS만 허용됩니다.
  • 사설·예약 IP 대역은 차단됩니다. 등록 시점과 전달 시점 양쪽에서 SSRF·DNS rebinding을 검증합니다.

자세한 차단 규칙은 웹훅 설정과 같습니다.

목록 조회​

GET /outbound-webhooks — 워크스페이스에 등록된 Outbound 웹훅을 모두 조회합니다.

  • Scope: webhooks:read

아래 요청은 웹훅 목록을 조회합니다.

curl "https://api.talk.zeroworks.ai/api/public/v1/outbound-webhooks" \
-H "Authorization: Bearer ztpat_..."

응답

{
"success": true,
"data": [
{
"id": "wh-1a2b-...",
"name": "My BFF webhook",
"url": "https://bff.example.com/zerotalk/webhook",
"event_filters": ["message.created", "channel.assigned"],
"match_filters": {},
"status": "active",
"failure_count": 0,
"last_delivered_at": "2026-06-02T01:23:45Z",
"created_at": "2026-06-01T00:00:00Z",
"updated_at": "2026-06-02T01:23:45Z"
}
]
}

last_delivered_at은 전달 이력이 없으면, error_message는 직전 전달이 성공했거나 이력이 없으면 null이 아니라 키가 생략됩니다. secret은 읽기 경로에서 항상 생략됩니다(생성·회전 응답에서만 1회 노출). 이 목록에는 페이지네이션이 없습니다.

생성​

POST /outbound-webhooks

  • Scope: webhooks:write
  • Body
필드타입설명
namestring웹훅 이름 (필수, 1–255자)
urlstring수신 HTTPS URL (필수, 최대 2048자)
event_filtersstring[] · 선택구독할 이벤트 목록. 비우면 전체 구독
match_filtersobject · 선택이벤트를 받을 상담의 조건. 생략하거나 {}이면 조건 없이 모두 받습니다 (전달 조건)

아래 요청은 message.created와 channel.assigned 이벤트를 받는 웹훅을 만듭니다.

curl -X POST "https://api.talk.zeroworks.ai/api/public/v1/outbound-webhooks" \
-H "Authorization: Bearer ztpat_..." \
-H "Content-Type: application/json" \
-d '{
"name": "My BFF webhook",
"url": "https://bff.example.com/zerotalk/webhook",
"event_filters": ["message.created", "channel.assigned"]
}'

응답 — 201. secret은 이 응답에서만 평문으로 반환됩니다.

{
"success": true,
"data": {
"id": "wh-1a2b-...",
"name": "My BFF webhook",
"url": "https://bff.example.com/zerotalk/webhook",
"event_filters": ["message.created", "channel.assigned"],
"match_filters": {},
"status": "active",
"secret": "9f8e7d...64자hex",
"failure_count": 0,
"created_at": "2026-06-01T00:00:00Z",
"updated_at": "2026-06-01T00:00:00Z"
}
}

생성 오류는 다음과 같습니다.

코드발생 조건대응
422 VALIDATION_FAILEDname·url 누락, 길이 초과, URL 형식 오류, match_filters의 알 수 없는 축이나 허용되지 않는 값error.details의 필드별 사유를 보고 고칩니다
403 WEBHOOK_LIMIT_EXCEEDED현재 플랜에서 만들 수 있는 웹훅 수를 넘음쓰지 않는 웹훅을 삭제합니다

단건 조회​

GET /outbound-webhooks/{id}

  • Scope: webhooks:read
  • 모니터링에 쓰는 status·failure_count·last_delivered_at을 포함하며, 직전 전달이 실패했을 때만 error_message가 추가됩니다 (시크릿 제외).

아래 요청은 웹훅 한 건의 상태를 조회합니다.

curl "https://api.talk.zeroworks.ai/api/public/v1/outbound-webhooks/wh-1a2b-..." \
-H "Authorization: Bearer ztpat_..."

응답

{
"success": true,
"data": {
"id": "wh-1a2b-...",
"name": "My BFF webhook",
"url": "https://bff.example.com/zerotalk/webhook",
"event_filters": ["message.created", "channel.assigned"],
"match_filters": {},
"status": "active",
"failure_count": 0,
"last_delivered_at": "2026-06-02T01:23:45Z",
"created_at": "2026-06-01T00:00:00Z",
"updated_at": "2026-06-02T01:23:45Z"
}
}

수정​

PATCH /outbound-webhooks/{id} — name·url·event_filters·match_filters를 부분 수정합니다 (보낸 필드만 변경).

  • Scope: webhooks:write

아래 요청은 이름과 구독 이벤트만 바꿉니다.

curl -X PATCH "https://api.talk.zeroworks.ai/api/public/v1/outbound-webhooks/wh-1a2b-..." \
-H "Authorization: Bearer ztpat_..." \
-H "Content-Type: application/json" \
-d '{ "name": "Renamed webhook", "event_filters": ["message.created"] }'
  • event_filters를 생략하면 구독은 그대로 유지됩니다. [](빈 배열)을 보내면 모든 필터가 초기화되어 전체 이벤트 구독으로 바뀝니다(생성 시 빈 필터와 같습니다). 이벤트 수신을 멈추려면 빈 필터를 보내지 말고 활성/비활성 전환으로 웹훅을 비활성화하세요.
  • match_filters를 생략하거나 null로 보내면 저장된 조건이 그대로 유지됩니다. {}를 보내면 조건이 모두 지워져 모든 이벤트를 받습니다.

전달 조건​

match_filters는 event_filters(이벤트 종류)와 별개로, 어떤 상담에서 난 이벤트를 받을지 좁히는 조건입니다. 대시보드 웹훅 설정의 「전달 조건」과 같습니다. 키는 조건 이름(축)이고 값은 문자열 배열입니다. 한 축 안의 값은 하나라도 맞으면 통과하고(OR), 여러 축은 모두 맞아야 통과합니다(AND). 빈 배열인 축은 조건이 없는 것으로 봅니다.

아래 요청은 사람 상담원이 담당하는 상담에서 고객이 보낸 메시지 이벤트만 받도록 설정합니다.

curl -X PATCH "https://api.talk.zeroworks.ai/api/public/v1/outbound-webhooks/wh-1a2b-..." \
-H "Authorization: Bearer ztpat_..." \
-H "Content-Type: application/json" \
-d '{ "match_filters": { "channel.assigned_type": ["human"], "message.sender_type": ["contact"] } }'
축허용 값
channel.provider채널 종류 코드 (예: kakao, naver, web, native)
channel.tag상담 태그 이름
channel.assigned_typeai · human
channel.assigned_member_id멤버 UUID, 또는 담당자가 없는 상담을 뜻하는 unassigned
message.sender_typemember · contact · system · bot · workflow · external · integration
  • 축은 최대 10개, 축마다 값은 최대 50개, 값 하나는 200자 이하입니다. 빈 문자열은 허용되지 않습니다.
  • channel.provider·channel.tag는 대소문자를 구분하지 않고 비교합니다. channel.assigned_type·message.sender_type은 위 표의 철자 그대로 보내야 합니다.
  • message.sender_type은 메시지 이벤트에만 적용됩니다. 채널 이벤트에는 발신자가 없으므로 이 조건과 무관하게 전달됩니다.
  • 아직 태그가 없는 상담의 이벤트는 channel.tag 조건과 무관하게 전달됩니다. 봇이 응대하는 구간의 첫 메시지 대부분이 여기에 해당합니다.
  • channel.tag는 태그 이름으로 비교하므로, 태그 이름을 바꾸면 이전 이름으로 건 조건은 더 이상 맞지 않습니다.
  • unassigned는 AI가 담당하는 상담을 포함하지 않습니다. channel.assigned_member_id가 ["unassigned"]뿐이면 channel.assigned_type과 함께 보낼 수 없습니다. 둘을 모두 만족하는 상담이 없기 때문입니다.

삭제​

DELETE /outbound-webhooks/{id}

  • Scope: webhooks:write
  • 응답: 204 No Content

시크릿 회전​

POST /outbound-webhooks/{id}/regenerate-secret — 서명 시크릿을 회전합니다.

  • Scope: webhooks:write
  • 새 시크릿은 응답에서만 평문으로 반환됩니다. 서버 측 grace 기간이 없으므로, 회전 직후 짧은 시간 동안 옛 시크릿으로 서명된 요청이 도착할 수 있습니다 (서명 검증 참고).
{ "success": true, "data": { "id": "wh-1a2b-...", "secret": "a1b2c3...64자hex" } }

활성/비활성 전환​

POST /outbound-webhooks/{id}/toggle — 웹훅을 활성/비활성으로 전환합니다.

  • Scope: webhooks:write
  • 웹훅이 연속 실패로 status=error가 되어 자동 비활성화된 경우, toggle로 재활성화하면 failure_count가 리셋되고 다시 active가 됩니다 (자동 비활성화 규칙은 전달 참고).
  • 재활성화하면 비활성 기간에 보류된 미전달분이 이벤트 접수 시점부터 24시간 윈도우 내에서 전달됩니다.
{ "success": true, "data": { "id": "wh-1a2b-...", "status": "active", "failure_count": 0 } }

전달 로그​

GET /outbound-webhooks/{id}/delivery-logs — 최근 전달 시도(성공 여부·HTTP 상태·에러·소요 시간)를 조회합니다.

  • Scope: webhooks:read
  • 쿼리: limit(기본 50, 최대 100), offset(기본 0, 최대 9,999) — offset 기반 페이징입니다. 응답은 data.items·data.total·data.has_more입니다.
  • limit이 100을 넘으면 오류 없이 100으로 보정합니다. limit이 양의 정수가 아니면 400 INVALID_LIMIT, offset이 0 이상 9,999 이하의 정수가 아니면 400 INVALID_OFFSET을 반환합니다.
  • 페이지를 시작할 수 있는 offset은 9,999까지입니다. 그 안에서 시작한 페이지는 요청한 limit만큼 돌려주므로 마지막 페이지가 10,000번째 이후의 로그를 포함할 수 있습니다. data.has_more는 다음 페이지의 offset이 9,999 이하일 때만 true이므로, has_more를 따라 페이지를 넘기면 이 범위 끝에서 정상적으로 멈춥니다.
{
"success": true,
"data": {
"items": [
{
"id": 1,
"webhook_id": "wh-1a2b-...",
"event_type": "message.created",
"success": true,
"http_status": 200,
"duration_ms": 142,
"created_at": "2026-06-02T01:23:45Z"
}
],
"total": 1,
"has_more": false
}
}

success(불리언)는 각 항목에 항상 포함되는 성공 여부 판별 필드입니다. http_status·error_message는 값이 없으면 null이 아니라 키가 생략됩니다. 성공한 시도(success: true)는 error_message가 빠지고, HTTP 응답을 받지 못한 시도는 http_status가 빠집니다.

참고​

  • 페이로드·서명·재시도·멱등 등 수신 측 처리 규약은 웹훅 문서를 참고하세요.
  • 콘솔 전체 흐름(웹훅 자가 등록 → 수신 → 렌더 → 답장)은 상담 콘솔 임베드 가이드에 있습니다.