예제
수신 웹훅 예제 — curl 한 줄, 토큰, 스레드, blocks 형식, CI 워크플로 알림
복사해서 바로 쓸 수 있는 수신 웹훅 예제입니다. 모든 예제의 <토큰> · <시크릿> 은 발급받은 값으로 바꿉니다. 필드와 상한은 수신 웹훅, 서식과 토큰 전량은 표현 레퍼런스에 있습니다.
1. 가장 짧은 알림
curl -X POST "https://collabops.ai/api/v2/integration/webhook/<토큰>" \
-H "Authorization: Bearer <시크릿>" \
-H "Content-Type: application/json" \
-d '{"text": "**배포 완료** `web` 0.4.2"}'성공하면 {"delivery_id": 1234, "deduped": false} 가 돌아오고, 채널에 굵은 글씨와 코드 칩이 든 메시지가 올라갑니다.
2. 마크다운 서식과 토큰
여러 줄 본문은 파일로 만들어 --data-binary @파일 로 보내면 셸 따옴표 걱정이 없습니다. JSON 문자열 안의 줄바꿈은 \n 으로 씁니다.
{
"title": "로그인 오류 핫픽스 배포",
"text": "**핫픽스 배포 완료**\n{{issue:slug=acme|key=42|label=로그인 오류}} 수정분이 반영됐습니다.\n\n- 변경 요청: {{cr:slug=acme|key=318|label=세션 만료 수정}}\n- 배포 시각: {{date:iso=2026-09-23T15:30:00+09:00|hasTime=1}}\n\n| 서비스 | 버전 |\n|:--|--:|\n| api | 1.8.2 |\n| web | 0.4.2 |"
}curl -X POST "https://collabops.ai/api/v2/integration/webhook/<토큰>" \
-H "Authorization: Bearer <시크릿>" \
-H "Content-Type: application/json" \
--data-binary @message.jsontitle 은 채널 목록과 알림의 미리보기로만 쓰이고 본문에는 보이지 않습니다. 이슈·변경 요청은 칩으로, 날짜는 날짜 칩으로 바뀝니다. 채팅에서 제목(#)은 일반 글처럼 보이므로 첫 줄은 굵게로 강조했습니다.

3. 스레드로 이어 보내기
첫 응답의 delivery_id 를 받아 두었다가 다음 요청의 parent_delivery_id 로 넣습니다. jq 가 없는 환경에서도 돌도록 sed 로 꺼냅니다.
URL="https://collabops.ai/api/v2/integration/webhook/<토큰>"
AUTH="Authorization: Bearer <시크릿>"
# 원글
RESP=$(curl -sS -X POST "$URL" -H "$AUTH" -H "Content-Type: application/json" \
-d '{"text": "**야간 배치 시작**"}')
PARENT=$(printf '%s' "$RESP" | sed -n 's/.*"delivery_id":\([0-9]*\).*/\1/p')
# 답글 — 원글 스레드에 붙는다
curl -sS -X POST "$URL" -H "$AUTH" -H "Content-Type: application/json" \
-d "{\"text\": \"1단계 완료 (12분)\", \"parent_delivery_id\": ${PARENT}}"답글의 delivery_id 를 다시 parent_delivery_id 로 써도 새 스레드가 생기지 않고 같은 스레드에 붙습니다.

4. blocks 형식 — 색 · 표 · 토글 · 이슈 칩
마크다운으로 안 되는 표현이 필요하면 blocks 로 보냅니다. 아래는 점검 결과를 표로 보여 주고, 실패 로그는 토글 안에 접어 두는 예입니다.
{
"blocks": [
{"id": "title", "type": "paragraph",
"children": [{"text": "야간 점검 결과", "marks": [{"type": "bold"}]}]},
{"id": "summary", "type": "paragraph", "children": [
{"text": "3건 중 "},
{"text": "1건 실패", "marks": [{"type": "bold"}, {"type": "textColor", "attrs": {"color": "red"}}]},
{"text": " — 추적 "},
{"type": "embed", "embedType": "entityRef",
"attrs": {"kind": "issue", "workspaceSlug": "acme", "entityKey": 57, "title": "인증서 자동 갱신"}}
]},
{"id": "c00", "type": "tableCell", "attrs": {"tableId": "t1", "rowIdx": 0, "colIdx": 0, "colCount": 2},
"children": [{"text": "점검"}]},
{"id": "c01", "type": "tableCell", "attrs": {"tableId": "t1", "rowIdx": 0, "colIdx": 1},
"children": [{"text": "결과"}]},
{"id": "c10", "type": "tableCell", "attrs": {"tableId": "t1", "rowIdx": 1, "colIdx": 0},
"children": [{"text": "DB 백업"}]},
{"id": "c11", "type": "tableCell", "attrs": {"tableId": "t1", "rowIdx": 1, "colIdx": 1},
"children": [{"text": "성공", "marks": [{"type": "textColor", "attrs": {"color": "green"}}]}]},
{"id": "c20", "type": "tableCell", "attrs": {"tableId": "t1", "rowIdx": 2, "colIdx": 0},
"children": [{"text": "인증서 만료"}]},
{"id": "c21", "type": "tableCell", "attrs": {"tableId": "t1", "rowIdx": 2, "colIdx": 1},
"children": [{"text": "7일 남음", "marks": [{"type": "textColor", "attrs": {"color": "red"}}]}]},
{"id": "log", "type": "toggle", "children": [{"text": "실패 로그 보기"}]},
{"id": "log-body", "parentId": "log", "type": "codeBlock", "attrs": {"language": "text"},
"children": [{"text": "cert-check: api.example.com expires in 7 days\nexit 1"}]}
]
}curl -X POST "https://collabops.ai/api/v2/integration/webhook/<토큰>" \
-H "Authorization: Bearer <시크릿>" \
-H "Content-Type: application/json" \
--data-binary @nightly.json
포인트
표는 셀 하나가 블록 하나입니다. 이 예제는 헤더 포함 6셀이라 표만으로 6블록, 전체 10블록입니다.
colCount 는 표의 첫 셀(rowIdx: 0, colIdx: 0)에만 적습니다.
표 칸의 색은 칸의 attrs 가 아니라 칸 안 글 조각에 textColor 마크로 칠합니다. 블록 attrs 의 색은 채팅에서 칠해지지 않습니다.
토글 안에 넣을 블록은 parentId 로 토글의 id 를 가리키고, 배열에서 토글 바로 뒤에 둡니다. 토글은 접힌 채 올라가고 누르면 펼쳐집니다.
블록 하나라도 규격에 어긋나면 메시지 전체가 422 로 거절되고 error.title 에 몇 번째 블록인지 나옵니다.
5. CI 워크플로에서 성공 · 실패 알림
배포 job 뒤에 성공 알림과 실패 알림 job 을 따로 둡니다. 알림은 부가 기능이므로 알림 요청이 실패해도 파이프라인은 초록으로 끝나야 합니다. curl 의 실패를 || HTTP_CODE="000" 으로 받아 응답 코드만 로그에 남기는 이유입니다.
name: deploy
triggers:
push:
branches: [main]
jobs:
deploy:
steps:
- name: deploy
run: ./scripts/deploy.sh # 실제 배포 명령
# ─────────────────────────────────────────
# 배포 성공 알림 — deploy 가 성공했을 때만
# ─────────────────────────────────────────
notify-success:
needs: [deploy]
if: "success()"
steps:
- name: notify
image: curlimages/curl:8.17.0 # curl 만 있으면 되는 가벼운 이미지
env:
# URL 과 시크릿은 서로 다른 값이다 — 시크릿 저장소에 따로 넣는다
COLLABOPS_WEBHOOK_URL: ${{ secrets.COLLABOPS_WEBHOOK_URL }}
COLLABOPS_WEBHOOK_SECRET: ${{ secrets.COLLABOPS_WEBHOOK_SECRET }}
REF_NAME: ${{ collabops.ref_name }}
COMMIT_SHA: ${{ collabops.sha }}
COMMIT_MESSAGE: ${{ collabops.commit_message }}
ACTOR: ${{ collabops.actor }}
run: |
# JSON 문자열에 넣기 전에 역슬래시와 큰따옴표를 이스케이프한다
esc() { printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'; }
TITLE=$(esc "$(printf '%s\n' "$COMMIT_MESSAGE" | head -1)") # 커밋 제목 한 줄만
SHORT_SHA=$(printf '%s' "$COMMIT_SHA" | cut -c1-8)
BODY=$(printf '{"text": "✅ **배포 성공** `%s` `%s`\\n%s\\n실행자: %s"}' \
"$REF_NAME" "$SHORT_SHA" "$TITLE" "$(esc "$ACTOR")")
# 알림이 실패해도 파이프라인을 실패시키지 않는다 — 응답 코드만 로그에 남긴다
HTTP_CODE=$(curl -sS -o /tmp/webhook-resp -w '%{http_code}' -X POST \
-H "Authorization: Bearer ${COLLABOPS_WEBHOOK_SECRET}" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: deploy-${SHORT_SHA}-success" \
--data "$BODY" \
"${COLLABOPS_WEBHOOK_URL}") || HTTP_CODE="000"
echo "webhook http=${HTTP_CODE} $(cat /tmp/webhook-resp 2>/dev/null)"
# ─────────────────────────────────────────
# 배포 실패 알림 — deploy 가 실패했을 때만
# ─────────────────────────────────────────
notify-failure:
needs: [deploy]
if: "failure()"
steps:
- name: notify
image: curlimages/curl:8.17.0
env:
COLLABOPS_WEBHOOK_URL: ${{ secrets.COLLABOPS_WEBHOOK_URL }}
COLLABOPS_WEBHOOK_SECRET: ${{ secrets.COLLABOPS_WEBHOOK_SECRET }}
REF_NAME: ${{ collabops.ref_name }}
COMMIT_SHA: ${{ collabops.sha }}
run: |
SHORT_SHA=$(printf '%s' "$COMMIT_SHA" | cut -c1-8)
BODY=$(printf '{"text": "❌ **배포 실패** `%s` `%s`\\n실행 로그를 확인해 주세요."}' \
"$REF_NAME" "$SHORT_SHA")
HTTP_CODE=$(curl -sS -o /tmp/webhook-resp -w '%{http_code}' -X POST \
-H "Authorization: Bearer ${COLLABOPS_WEBHOOK_SECRET}" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: deploy-${SHORT_SHA}-failure" \
--data "$BODY" \
"${COLLABOPS_WEBHOOK_URL}") || HTTP_CODE="000"
echo "webhook http=${HTTP_CODE} $(cat /tmp/webhook-resp 2>/dev/null)"실행 흐름
deploy
├── 성공 → notify-success (✅ 배포 성공 알림)
└── 실패 → notify-failure (❌ 배포 실패 알림)
알림 요청이 실패해도 → 로그에 http=000 또는 4xx 만 남고 job 은 성공포인트
URL 과 시크릿을 시크릿 저장소에 따로 넣고 ${{ secrets.… }} 로 읽습니다. 워크플로 파일에 값을 적지 않습니다.
커밋 메시지처럼 따옴표나 역슬래시가 들어갈 수 있는 값은 esc 로 이스케이프한 뒤 JSON 에 넣습니다. 이스케이프하지 않으면 본문이 JSON 이 아니게 되어 422 가 납니다.
Idempotency-Key 에 커밋 SHA 와 결과를 넣어 두면 job 을 다시 실행해도 같은 알림이 두 번 뜨지 않습니다.
워크플로 문법은 CI/CD 워크플로 문법을, 조건식은 표현식을 참고하세요.
6. 응답 코드 확인하기
스크립트에서 결과를 판단하려면 -w '%{http_code}' 로 상태 코드를 따로 받습니다. 202 가 아니면 본문의 error.title 에 사유가 있습니다.
CODE=$(curl -sS -o resp.json -w '%{http_code}' -X POST "$URL" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"text": "점검 완료", "status": "ok"}')
echo "$CODE"; cat resp.json
# 422
# {"error":{"code":"VALIDATION_ERROR","title":"요청 형식이 올바르지 않습니다: extra_forbidden at status", …}}받지 않는 필드(status)를 넣어서 422 가 났습니다. text 형식이 받는 필드는 text · title · parent_delivery_id 뿐입니다.