수신 웹훅
외부 도구가 HTTP 요청 한 번으로 채널에 메시지를 올리는 방법 — 요청 형식, 응답, 오류, 상한, 스레드
수신 웹훅은 CI 파이프라인, 모니터링 도구, 사내 스크립트 같은 외부 도구가 HTTP 요청 한 번으로 채널에 메시지를 올리는 주소입니다. 이 문서는 요청을 어떻게 보내고, 어떤 응답이 오고, 무엇이 제한되는지 설명합니다. 본문에 쓸 수 있는 서식과 토큰은 표현 레퍼런스에, 복사해서 바로 쓸 수 있는 예시는 예제에 있습니다. 외부 도구가 같은 URL 과 시크릿으로 채널 대화를 읽어 가는 방법은 채널 읽기에 있습니다.
발급하기
채널 관리자가 채널 화면에서 발급합니다. 한 채널에 웹훅을 여러 개 만들 수 있으니 도구마다 따로 발급하면 어느 도구가 보낸 메시지인지 채널에서 바로 구분됩니다.
- 채널 메뉴에서 채널 설정을 눌러 채널 편집 창을 엽니다. 아래쪽 웹훅 버튼을 누르면 웹훅 관리하기 창이 열립니다.

-
+ 발급하기를 누르고 웹훅 프로필 표시에 채널에 보일 이름을 적습니다. 이름은 같은 채널 안에서 겹치면 안 됩니다. 프로필 사진은 선택이고, 메시지는 이 이름과 사진으로 올라갑니다.
-
외부 도구가 이 채널의 대화도 읽어야 하면 채널 대화 읽기 허용을 켭니다. 기본은 꺼져 있고, 켜면 같은 URL 과 시크릿으로 채널 읽기를 할 수 있습니다. 발급한 뒤에도 바꿀 수 있습니다.

- 저장을 누르면 URL, 시크릿, 그리고 붙여 넣어 바로 시험할 수 있는 curl 명령(보내보기)이 나옵니다.

발급이 끝나면 두 가지 값을 받습니다.
| 값 | 모양 | 설명 |
|---|---|---|
| URL | https://collabops.ai/api/v2/integration/webhook/<토큰> | 메시지를 보낼 주소입니다. 경로의 토큰이 게시할 채널을 정합니다. |
| 시크릿 | 문자열 | Authorization: Bearer <시크릿> 헤더로 보냅니다. 발급 직후 한 번만 보입니다. |
URL 과 시크릿은 서로 다른 값이고 둘 다 비밀입니다. 저장소에 커밋하지 말고 CI 의 시크릿 저장소에 각각 넣으세요. 시크릿은 다시 볼 수 없으므로 잃어버렸다면 웹훅을 삭제하고 새로 발급합니다. 삭제하면 기존 URL 은 즉시 무효가 됩니다.
self-hosted 로 설치했다면 https://collabops.ai 자리에 설치한 서버 주소가 들어갑니다. 발급 화면에 보이는 URL 을 그대로 쓰면 됩니다.
켜기 · 끄기 · 이름 변경 · 삭제
웹훅 관리하기 창의 카드마다 켜기/끄기 스위치와 ⋯ 메뉴가 있습니다. ⋯ 메뉴에서 전송 기록, 채널 대화 읽기 허용, 이름 변경, 삭제를 고릅니다. 전송 기록은 채널 멤버 누구나 볼 수 있고, 나머지는 채널 관리자만 바꿀 수 있습니다.

| 하는 일 | 결과 |
|---|---|
| 끄기 | URL 과 시크릿은 그대로입니다. 꺼져 있는 동안 오는 요청은 401 로 거절되고, 다시 켜면 같은 URL 로 받습니다. |
| 삭제 | 되돌릴 수 없습니다. URL 은 즉시 무효가 되고 이후 요청은 404 입니다. |
| 채널이 삭제됨 | 웹훅이 자동으로 꺼집니다. 이후 요청은 401 입니다. |
요청 형식
| 항목 | 값 | 필수 |
|---|---|---|
| 메서드 | POST | 예 |
Authorization 헤더 | Bearer <시크릿> | 예 |
Content-Type 헤더 | application/json | 권장 |
Idempotency-Key 헤더 | 200자 이하 문자열. 재전송돼도 한 번만 게시합니다 (아래 중복 방지) | 아니오 |
| 본문 | JSON 객체, 64 KiB 이하 | 예 |
curl -X POST "https://collabops.ai/api/v2/integration/webhook/<토큰>" \
-H "Authorization: Bearer <시크릿>" \
-H "Content-Type: application/json" \
-d '{"text": "**배포 완료** `web` 0.4.2"}'본문 — text 와 blocks
본문은 두 형식 중 정확히 하나를 씁니다. text 와 blocks 를 함께 넣거나 둘 다 빼면 422 입니다.
| 형식 | 언제 쓰나 | 받는 필드 |
|---|---|---|
text | 대부분의 알림. 마크다운으로 충분할 때 | text · title · parent_delivery_id |
blocks | 글자색, 토글, 스프린트 참조처럼 마크다운으로 표현할 수 없는 것이 필요할 때 | blocks · parent_delivery_id |
text 형식
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
text | string | 예 | 마크다운입니다. 토큰을 쓰면 이슈 칩과 날짜 칩이 됩니다. |
title | string | 아니오 | 채널 목록의 마지막 메시지 미리보기와 알림에만 쓰이고 메시지 본문에는 나타나지 않습니다. 없으면 본문에서 만듭니다. |
parent_delivery_id | 양의 정수 | 아니오 | 이 메시지를 답글로 붙일 원글의 delivery_id 입니다 (아래 스레드). |
blocks 형식
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
blocks | array | 예 | 블록 배열입니다. 블록마다 id 와 type 이 필요합니다. |
parent_delivery_id | 양의 정수 | 아니오 | text 형식과 같습니다. |
블록 종류와 모양은 표현 레퍼런스에 있습니다.
표에 없는 필드는 받지 않습니다. blocks 형식에 title 을 넣거나 status 같은 필드를 덧붙이면 무시되지 않고 422 로 거절됩니다. 받아 놓고 조용히 버리면 보낸 쪽이 왜 화면에 안 보이는지 알 수 없기 때문입니다.
응답
요청을 받으면 게시를 기다리지 않고 바로 202 Accepted 를 돌려줍니다. 채널 게시는 그 뒤에 비동기로 이루어집니다.
{"delivery_id": 1234, "deduped": false}| 필드 | 설명 |
|---|---|
delivery_id | 이 요청의 수신 기록 번호입니다. 스레드로 이어 보낼 때 parent_delivery_id 로 씁니다. |
deduped | 같은 Idempotency-Key 로 이미 받은 요청이면 true 입니다. 이때 delivery_id 는 처음 받은 기록의 번호입니다. |
202 는 받았다는 뜻이지 채널에 올라갔다는 뜻이 아닙니다. 게시 결과는 아래 전송 결과 확인의 전송 기록에서 봅니다.
오류 응답
실패하면 아래 모양의 JSON 이 옵니다. error.title 에 거절 사유가, error.request_id 에 문의할 때 알려 줄 추적 번호가 들어 있습니다. 같은 추적 번호가 X-Request-Id 응답 헤더에도 실립니다.
{
"error": {
"code": "VALIDATION_ERROR",
"title": "요청 형식이 올바르지 않습니다: exactly one of text or blocks is required",
"request_id": "3f1c9a52-8e0b-4c1d-9f7a-2b6e4d8c1a90",
"upstream_status": 422
}
}| 상태 | 언제 | 어떻게 하나 |
|---|---|---|
| 401 | 시크릿이 틀렸거나 Authorization 헤더가 없을 때, 웹훅이 꺼져 있을 때 (채널이 삭제돼 자동으로 꺼진 경우 포함) | 시크릿을 고치거나 웹훅을 켠 뒤 보냅니다. |
| 404 | URL 의 토큰에 해당하는 웹훅이 없을 때 (삭제된 웹훅 포함) | 새로 발급한 URL 로 보냅니다. |
| 413 | 요청 전체가 1 MiB 를 넘을 때 | 본문을 줄입니다. |
| 422 | 본문이 JSON 이 아니거나, 필드가 맞지 않거나, 상한을 넘었거나, parent_delivery_id 를 쓸 수 없을 때 | error.title 의 사유대로 본문을 고칩니다. 그대로 다시 보내면 또 422 입니다. |
| 429 | 웹훅 하나에 분당 60회를 넘었을 때 | Retry-After 헤더의 초만큼 기다린 뒤 보냅니다. |
| 503 · 502 · 504 | 일시적인 내부 오류나 응답 지연 | 잠시 뒤 같은 요청을 다시 보냅니다. Idempotency-Key 를 붙여 두면 두 번 게시될 걱정이 없습니다. |
상한
| 무엇 | 상한 | 넘으면 |
|---|---|---|
| 본문 크기 | 64 KiB (65,536바이트, 받은 본문 그대로 셈) | 422 |
| 요청 전체 크기 | 1 MiB | 413 |
| 블록 수 | 200개 | 422 |
| 전송 빈도 | 웹훅 하나당 분당 60회 | 429 |
Idempotency-Key 길이 | 200자 | 422 |
블록 수는 변환된 뒤의 블록으로 셉니다. text 형식도 마크다운이 블록으로 바뀐 뒤의 개수입니다. 문단, 제목, 목록 항목은 하나가 블록 하나이고, 표는 셀 하나가 블록 하나입니다. 3열 10행 표는 그것만으로 30블록입니다. 공백뿐인 text 처럼 블록이 하나도 나오지 않는 본문도 422 로 거절됩니다.
긴 보고는 한 메시지에 몰아넣지 말고 스레드로 나눠 보내세요.
중복 방지
네트워크 오류로 응답을 못 받으면 발신 도구는 같은 요청을 다시 보내게 됩니다. Idempotency-Key 헤더에 알림마다 고유한 값을 넣어 두면 같은 키로 다시 와도 채널에는 한 번만 올라갑니다. 두 번째 응답은 deduped: true 와 처음 받은 delivery_id 를 돌려줍니다.
키를 넣지 않으면 요청마다 새 메시지가 됩니다. 커밋 SHA 와 이벤트 종류를 이은 deploy-3f9c2a71-success 처럼 같은 알림이면 늘 같은 값이 나오도록 만드세요. 401·422 처럼 거절된 요청은 키를 기억하지 않으므로, 본문을 고쳐 같은 키로 다시 보내면 정상적으로 처리됩니다.
스레드로 이어 보내기
응답으로 받은 delivery_id 를 다음 요청의 parent_delivery_id 에 넣으면 그 메시지의 답글로 올라갑니다. 채팅 내부의 메시지 번호는 몰라도 됩니다.
| 원글 상태 | 결과 |
|---|---|
| 채널에 올라감 | 답글로 붙습니다. |
| 아직 게시 중 | 원글이 올라갈 때까지 기다렸다가 붙습니다. 응답은 202 입니다. |
| 원글 자체가 답글 | 원글이 속한 스레드에 붙습니다. 답글의 답글은 만들어지지 않습니다. |
| 게시에 실패함 | 이 메시지도 게시되지 않습니다. 응답은 202 이고 전송 기록에 남습니다. |
| 다른 웹훅의 번호이거나 없는 번호 | 422 |
| 접수 단계에서 거절된 요청 | 422 |
전송 결과 확인
웹훅 관리하기 창의 카드마다 마지막 수신 상태가 보이고, ⋯ 메뉴의 전송 기록에서 요청 한 건 한 건의 결과를 최신순으로 볼 수 있습니다. 한 건마다 받은 시각, 상태, 본문 형식(텍스트·블록), 스레드 답글 여부가 나옵니다. 게시된 건은 메시지 보기로 채널의 그 메시지를 열 수 있습니다. 거절된 건에는 error.title 의 콜론 뒤에 붙는 것과 같은 영문 사유가 보이고, 본문 검증에 실패한 건은 원문 보기로 보낸 JSON 을 확인할 수 있습니다.

| 화면 표시 | 상태 값 | 뜻 |
|---|---|---|
| 접수됨 | accepted | 받았고 아직 게시 전입니다. |
| 성공 | ok | 채널에 올라갔습니다. |
| 부모 메시지 대기 | waiting_parent | 원글이 게시되기를 기다리는 답글입니다. |
| 부모 메시지 실패 | parent_failed | 원글 게시가 실패해 이 답글도 올리지 않았습니다. 새 요청으로 다시 보냅니다. |
| 채팅 전송 실패 | chat_error | 게시 단계에서 실패했습니다. |
| 채널 없음 | channel_gone | 채널이 사라졌습니다. 웹훅은 자동으로 꺼집니다. |
| 인증 실패 | unauthorized | 401 로 거절됐습니다. |
| 요청 과다 | rate_limited | 429 로 거절됐습니다. 1분 안에서 처음 넘은 요청만 남습니다. |
| 형식 오류 | invalid_payload | 422 로 거절됐습니다. |
| 용량 초과 | too_large | 본문 64 KiB 또는 블록 200개를 넘어 422 로 거절됐습니다. 본문은 남기지 않습니다. |
웹훅을 찾지 못한 404, 1 MiB 를 넘은 413, 일시적인 5xx 는 기록되지 않으므로 발신 쪽 로그에서 응답 코드를 확인하세요.