Webhook
최종 수정: 2026년 7월 3일
옷가게 쇼핑몰을 운영한다고 생각해 보세요. 새 상품을 등록할 때마다 매번 직접 챙겨야 하는 뒷일이 있습니다. 상품 설명을 다른 나라 말로 번역해 두거나, 등록 사실을 사내 메신저에 알리는 일 같은 것입니다. 이런 뒷일을 사람이 매번 손으로 하지 않고, 상품이 등록되는 순간 바깥에 있는 프로그램에 자동으로 알려서 대신 처리하게 만들 수 있습니다. 이 "어떤 일이 생기면 미리 정해 둔 곳에 자동으로 알려 주는 장치"가 Webhook입니다.
가게 문에 달아 둔 초인종에 비유할 수 있습니다. 손님이 문을 열고 들어오면(상품이 등록되면) 초인종이 저절로 울려서, 안쪽에 있는 직원(바깥 프로그램)이 "손님 오셨네" 하고 곧바로 움직이기 시작합니다. 누군가 계속 문 앞을 지켜보고 있을 필요가 없습니다. Webhook은 그 초인종처럼, 정해 둔 일이 일어나는 순간 정해 둔 곳으로 요청을 보냅니다.
이 페이지에서는 Webhook이 무엇이고 어떤 경우에 쓰는지 먼저 살펴본 뒤, 옷가게 Space에 Webhook을 직접 만들어 봅니다. 바깥 프로그램이 돌려준 결과를 다시 상품에 채워 넣는 WriteBack도 함께 다룹니다.
Webhook이 하는 일
Webhook은 세 가지를 미리 정해 두는 것으로 이루어집니다.
- 언제 보낼지: 어떤 일이 일어났을 때 요청을 보낼지 정합니다. 예를 들어 "상품(Content)이 새로 등록됐을 때"로 정할 수 있습니다.
- 어디로 보낼지: 요청을 받을 바깥 프로그램의 주소를 정합니다. 인터넷 주소(URL) 하나를 적어 둡니다.
- 켤지 끌지: 이 Webhook을 지금 켜 둘지(ACTIVE), 잠시 꺼 둘지(INACTIVE)를 정합니다. 꺼 두면 정해 둔 일이 일어나도 요청을 보내지 않습니다.
정해 둔 일이 실제로 일어나면, Webhook은 적어 둔 주소로 그 사실을 알리는 요청을 보냅니다. 요청에는 무슨 일이 일어났는지, 어떤 상품에서 일어났는지 같은 정보가 담겨 갑니다. 요청을 받은 바깥 프로그램은 그 정보를 보고 자기 할 일을 합니다.
어떤 변화에 요청을 보내나
요청을 부르는 "일"은 Space 안의 자원에 일어나는 변화입니다. 상품 같은 Content, 업로드한 파일인 Media, 양식 틀인 Content Type에 무언가 일어났을 때를 고를 수 있습니다.
자원마다 고를 수 있는 변화는 다음과 같습니다.
| 변화 | 언제 일어나나 | 옷가게 예시 |
|---|---|---|
| Create | 새로 만들어졌을 때 | 새 상품을 등록함 |
| Save | 내용을 고쳐 저장했을 때 | 상품 설명을 고쳐 저장함 |
| Delete | 삭제됐을 때 | 단종된 상품을 지움 |
| Publish | 발행해 외부에 공개했을 때 | 상품을 사이트에 공개함 |
| Unpublish | 발행을 취소했을 때 | 품절 상품을 사이트에서 내림 |
| Archive | 보관 처리했을 때 | 지난 시즌 상품을 보관함 |
| Unarchive | 보관을 풀었을 때 | 보관했던 상품을 되살림 |
예를 들어 "상품이 새로 등록될 때마다 요청을 보내라"는 "상품(Content)의 Create"를 고르는 것입니다.
한 Webhook에 여러 가지 변화를 함께 고를 수도 있습니다. "상품이 등록될 때"와 "상품이 수정될 때"를 모두 고르면, 둘 중 어느 쪽이 일어나도 요청이 갑니다.
조건을 걸어 좁히기
고른 변화가 일어났다고 해서 항상 요청을 보내고 싶지 않을 때가 있습니다. 예를 들어 "모든 Content가 아니라 '상품' 양식으로 만든 Content가 등록됐을 때만" 받고 싶을 수 있습니다. 이럴 때는 필터를 걸어 요청을 보낼 경우를 좁힙니다.
필터 하나는 "무엇을 기준으로, 어떻게 비교할지" 한 줄로 이루어집니다. 무엇을 기준으로 거를지는 네 가지 중에서 고릅니다.
- 어떤 양식으로 만든 항목인지: 예를 들어 "상품" Content Type으로 만든 Content에만 요청을 보냅니다. 가장 자주 쓰는 조건입니다.
- 특정 항목 하나인지: 정해 둔 그 항목 하나에서 일어난 변화에만 요청을 보냅니다.
- 누가 만든 항목인지: 특정 사람이 만든 항목에만 요청을 보냅니다.
- 누가 마지막으로 고친 항목인지: 특정 사람이 마지막으로 수정한 항목에만 요청을 보냅니다.
비교하는 방식도 함께 고릅니다. 정한 값과 같을 때만, 다를 때만, 정해 둔 여러 값 중 하나에 해당할 때만, 그 어느 것에도 해당하지 않을 때만, 또는 정한 형식(패턴)에 맞거나 맞지 않을 때만으로 좁힐 수 있습니다.
콘텐츠 스튜디오의 트리거 설정에서 필터 추가로 조건을 한 줄씩 더합니다. 필터를 여러 개 걸면 그 조건을 모두 만족하는 경우에만 요청이 가고, 하나도 걸지 않으면 고른 변화가 일어날 때마다 요청이 갑니다.
외부 프로그램이 원하는 모양으로 보내기
따로 정하지 않으면, 요청에는 변화가 일어난 항목의 정보가 통째로 담겨 갑니다. 예를 들어 상품 "스테인리스 텀블러 500ml"이 등록되면, 요청에 담겨 가는 내용은 대략 이런 모양입니다.
{
"sys": { "id": "3trmXRM3RqbgSnifyg7OGhwhlqvAvq", "type": "Content" },
"fields": {
"productName": { "ko-KR": "스테인리스 텀블러 500ml" }
}
}(실제로는 더 많은 정보가 담기며, 위는 일부만 추린 모양입니다.) 바깥 프로그램이 이 안에서 필요한 값을 골라 쓰면 됩니다. 하지만 "이런 모양으로만 받겠다"고 형식이 정해진 프로그램도 있습니다. 그럴 때는 콘텐츠 스튜디오의 페이로드에서 Webhook 페이로드 커스터마이즈를 골라, 보낼 모양을 직접 적어 둡니다.
보낼 모양을 적되, 위 데이터에서 값을 끌어다 넣을 자리에는 자리표시자를 씁니다. 자리표시자는 { /payload/… } 모양입니다. 여기서 payload는 위에 보인 그 항목 전체를 가리키고, 그 뒤 경로로 원하는 값을 콕 집습니다.
{ /payload/sys/id }→ 위 데이터의sys안id(상품의 고유 번호){ /payload/fields/productName/ko-KR }→fields안productName의ko-KR(한국어 상품명).fields/뒤에는 Field의 ID(상품명이면productName)와 언어 코드(한국어면ko-KR)를 차례로 붙입니다.
예를 들어 번역 프로그램이 "번역할 글과 상품 번호를 이 모양으로 달라"고 한다면, 페이로드를 이렇게 적습니다.
{
"id": "{ /payload/sys/id }",
"text": "{ /payload/fields/productName/ko-KR }"
}그러면 텀블러 상품이 등록되는 순간, 자리표시자가 실제 값으로 바뀌어 이렇게 전달됩니다.
{
"id": "3trmXRM3RqbgSnifyg7OGhwhlqvAvq",
"text": "스테인리스 텀블러 500ml"
}같은 자리표시자는 보내는 주소(URL)나 헤더 값에도 넣을 수 있고, 보내는 방식(method)과 형식(JSON 또는 폼 형식)도 함께 고를 수 있습니다. 가리킨 경로에 값이 없으면 그 자리는 빈 값이 됩니다.
외부 API 키처럼 남에게 보이면 안 되는 값은 헤더를 추가할 때 타입을 Secret으로 지정해 둡니다. 그러면 그 값은 가려져 저장되고 최종 사용자에게 노출되지 않습니다.
받은 응답으로 되돌려 채우기: WriteBack
보내는 요청의 모양을 정하는 것이 앞 절이라면, WriteBack은 돌아온 응답을 다룹니다. Webhook이 바깥 프로그램을 호출하고 정상(2xx) 응답을 받으면, 그 응답을 가공해 Space 안의 Content나 Media를 만들거나, 고치거나, 발행·삭제할 수 있습니다. 사람이 결과를 복사해 붙여 넣는 과정이 사라집니다.
옷가게 번역 예시를 끝까지 따라가 보겠습니다. 앞에서 새 상품의 한국어 상품명을 번역 프로그램에 보냈습니다. 번역 프로그램이 이렇게 응답한다고 하겠습니다.
{ "translated": "Stainless Tumbler 500ml" }이 번역문을 같은 상품의 영문 상품명 칸에 채우는 WriteBack을 구성합니다. 콘텐츠 스튜디오의 WriteBack에서 오퍼레이션 추가로 Content의 Update(수정) 동작을 하나 더하고 이렇게 정합니다.
- 대상: 따로 정하지 않으면 변화가 일어난 그 상품(텀블러)이 대상이 됩니다.
- 채울 칸과 언어: 영문 상품명이므로 칸은 상품명(
productName), 언어는 영어(en-US). - 넣을 값: 응답의 번역문을 가리키는
{ /response/translated }.
그러면 텀블러 상품의 영문 상품명 칸이 Stainless Tumbler 500ml로 채워집니다.
이런 WriteBack은 외부 LLM과 잇는 데 특히 쓸모가 있습니다. 예를 들어 상품 설명을 보내 만든 홍보 이미지를 Media로 저장하거나, 생성된 소개 문구로 새 Content를 만드는 흐름을 Webhook 하나로 구성할 수 있습니다.
동작 순서
- 변화가 일어나면 Webhook이 정한 모양으로 바깥 프로그램에 요청을 보냅니다.
- 응답이 2xx이면, 더해 둔 WriteBack 동작(오퍼레이션)을 위에서부터 차례대로 실행합니다.
- 각 동작은 서로 독립적이라 하나가 실패해도 나머지는 계속 실행됩니다.
- WriteBack으로 생긴 생성·수정·삭제·발행도 다시 변화로 잡혀, 그 변화를 구독하는 다른 Webhook을 연달아 부를 수 있습니다. 끝없이 도는 순환은 Webhook을 만들거나 고칠 때 자동으로 검사되어 막힙니다.
WriteBack으로 만들어지거나 바뀐 자원의 만든 사람(createdBy)은 그 변화를 일으킨 사용자가 됩니다.
값 표현식
채울 값은 앞에서 본 것과 같은 자리표시자({ /… })로 가져옵니다. 단, WriteBack에서는 가리킬 수 있는 뿌리가 두 개입니다.
| 뿌리 | 가리키는 대상 |
|---|---|
{ /response/… } | 바깥 프로그램이 돌려준 응답 본문 |
{ /payload/… } | 변화가 일어난 원래 항목(Content/Media) |
- 하나의 자리표시자만 단독으로 쓰면(
{ /response/url }) 값의 원래 형태(글자·숫자·묶음)를 그대로 가져옵니다. - 글자와 섞어 쓰면(
"생성됨: { /payload/sys/id }") 하나의 글자로 이어 붙습니다. - 응답이 JSON이 아니라 글자 그대로 올 때는
{ /response }로 응답 전체를 글자로 받습니다(그 안의 하위 경로는 가리킬 수 없습니다).
Content 다루기 ($content)
응답으로 Content를 만들거나 바꾸는 동작입니다. 정하는 항목은 다음과 같습니다.
| 항목 | 설명 |
|---|---|
action | Create · Update · Delete · Publish · Unpublish · Archive · Unarchive 중 하나 (대소문자 구분 안 함) |
contentType | 만들 Content의 Content Type. Create에 필수이며 sys.id만 있으면 됩니다. |
target | 바꿀 대상 Content의 ID(자리표시자로 지정). 생략하면 변화가 일어난 그 항목이 대상입니다. (Update·Delete·Publish·Unpublish·Archive·Unarchive) |
locale | fields 값을 기록할 언어. 코드(예: ko-KR)나 그 코드로 풀리는 자리표시자. 생략 시 Space의 기본 언어. (Create·Update) |
fields | 칸 이름 → 넣을 값(자리표시자). (Create·Update) |
publish | Create·Update 뒤 바로 발행할지(기본 켜짐). 끄면 발행하지 않고 Draft로 둡니다. |
unpublish | Delete 시 발행을 먼저 자동으로 내린 뒤 삭제할지(기본 켜짐). 끄면 대상이 이미 삭제 가능한 상태가 아니면 삭제가 실패합니다. |
Media 다루기 ($media)
응답으로 받은 파일을 Media로 들여놓거나 Media 상태를 바꾸는 동작입니다.
| 항목 | 설명 |
|---|---|
action | Create · Delete · Publish · Unpublish · Archive · Unarchive (수정 Update는 없음) |
source | 들여놓을 파일을 가리키는 자리표시자. (Create) |
encoding | 파일을 받는 방식: Url(주소에서 내려받음) 또는 Base64(응답에 담긴 파일 데이터를 풀어 저장) |
locale | 들여놓은 파일과 제목·설명을 기록할 언어. Content와 같은 규칙. (Create) |
title · description | 그 언어의 미디어 제목·설명(자리표시자, 선택). (Create) |
target | 바꿀 대상 Media의 ID. 생략하면 변화가 일어난 그 항목. (Delete·Publish·Unpublish·Archive·Unarchive) |
publish | Create 후 처리가 끝나면 바로 발행할지(기본 켜짐). |
unpublish | Delete 시 발행을 먼저 내린 뒤 삭제할지(기본 켜짐). |
$media는 Content의 칸 값으로도 쓸 수 있습니다. 응답으로 받은 파일을 Media로 들여놓은 뒤 그 Media를 칸에 바로 연결해, "이미지가 들어 있는 Content"를 한 번에 만들 수 있습니다.
Media의
Delete·Archive·Unarchive는 목록상의 정보뿐 아니라 저장소에 있는 실제 파일까지 함께 처리합니다.
설정하는 법과 예시
콘텐츠 스튜디오의 WriteBack에서 오퍼레이션 추가로 동작을 하나씩 더합니다. 화면에서 항목을 채우는 Visual 방식과, 아래 같은 writeBacks 묶음을 직접 적는 JSON 방식을 오갈 수 있습니다. 어느 쪽으로 해도 결과는 같습니다.
외부 LLM이 만든 이미지를 붙여 새 상품 만들기. 응답에 담긴 이미지 주소를 Media로 들여놓아, 그 이미지를 가진 Content를 새로 만듭니다.
{
"writeBacks": [
{
"$content": {
"action": "Create",
"contentType": { "sys": { "id": "<상품 Content Type의 sys.id>" } },
"fields": {
"productName": "{ /payload/fields/productName/ko-KR }",
"photo": { "$media": { "source": "{ /response/data/0/url }", "encoding": "Url" } }
}
}
}
]
}변화가 일어난 그 상품 보강하기. target을 생략하면 변화가 일어난 Content가 대상입니다. 응답의 값을 그 상품 칸에 채웁니다.
{
"writeBacks": [
{
"$content": {
"action": "Update",
"locale": "en-US",
"fields": { "productName": "{ /response/translated }" }
}
}
]
}검수 후 발행하려고 Draft로만 만들기. publish를 꺼 두면 발행하지 않고 초안으로 남겨, 사람이 확인한 뒤 발행할 수 있습니다.
{
"writeBacks": [
{
"$content": {
"action": "Create",
"contentType": { "sys": { "id": "<Content Type의 sys.id>" } },
"fields": { "text": "{ /response/choices/0/message/content }" },
"publish": false
}
}
]
}응답 이미지를 독립 Media로 들여놓기. 주소로 받으면 encoding을 Url로, 응답에 담긴 파일 데이터로 받으면 Base64로 둡니다.
{
"writeBacks": [
{ "$media": { "action": "Create", "source": "{ /response/data/0/url }", "encoding": "Url" } }
]
}Delete·Publish·Unpublish·Archive·Unarchive는 모양이 같습니다. action과 바꿀 대상 target만 적으면 그 대상의 상태만 바뀝니다(대상을 생략하면 변화가 일어난 그 항목). 예를 들어 응답이 알려 준 Content를 발행하려면 이렇게 적습니다.
{
"writeBacks": [
{ "$content": { "action": "Publish", "target": "{ /response/contentId }" } }
]
}동작을 여러 개 더하면 적은 순서대로 실행됩니다. "번역 결과를 상품에 채우고, 이어서 홍보 이미지를 Media로 들여놓기"처럼 잇따라 두는 식입니다.
실행 결과 확인
각 WriteBack 동작이 어떻게 됐는지는 Webhook의 호출 기록에서 확인합니다. 동작마다 다음이 남습니다.
| 항목 | 설명 |
|---|---|
| 순서 | 더해 둔 동작 중 몇 번째인지 |
| 대상 | Content인지 Media인지 |
| 동작 | 수행한 action |
| 상태 | Success(성공) · Failed(실패) · Skipped(건너뜀) |
| 결과 ID | 만들어지거나 바뀐 자원의 ID(있을 때) |
| 오류 | 실패했을 때의 메시지 |
성공한 동작은 결과 ID로 어떤 자원이 생기거나 바뀌었는지 따라갈 수 있습니다. 응답이 2xx가 아니면 WriteBack은 아예 실행되지 않아 결과 기록도 비어 있습니다.
알아 둘 것
- 요청을 보내는 변화는 하나만. 한 Webhook이 "등록(
Create)"과 "발행(Publish)"을 동시에 잡으면 바깥 프로그램이 두 번 불릴 수 있습니다. WriteBack을 쓰는 Webhook은 변화 하나만 고르세요. - 값을 옮길 뿐 계산은 못 합니다. 응답에서 값을 꺼내 넣는 것까지만 하며, 조건 분기나 반복 같은 처리는 하지 않습니다.
- 다시 시도하지 않습니다. 처리 도중 문제가 생기면 그 동작은 그대로 유실될 수 있습니다.
- 함부로 못 만들게. WriteBack으로만 만들어지는 Content Type은 일반 사용자가 직접 만들지 못하도록 권한을 좁혀 두는 것이 좋습니다.
옷가게 Webhook 만들기
이제 옷가게 Space에 Webhook을 하나 만들어 봅니다. "새 상품이 등록되면, 미리 준비해 둔 바깥 번역 프로그램에 그 사실을 알린다"는 Webhook입니다. 요청을 받을 바깥 프로그램의 주소는 https://example.com/translate라고 하겠습니다.
- 옷가게 Space의 설정에서 Webhook 화면을 여세요.
- 오른쪽 위의 생성 버튼을 누르세요.
- 이름 칸에
새 상품 번역 알림을 입력하세요. 이 이름은 나중에 어떤 Webhook인지 알아보기 위한 것입니다. - 요청을 보낼 변화를 정하세요. 특정 변화에만 보내려면 특정 트리거 이벤트 선택을 고른 뒤 원하는 변화(여기서는 상품(Content)의
Create)를 지정하고, 모든 변화에 보내려면 모든 이벤트에 대해 트리거를 고릅니다. - URL 칸에 요청을 받을 바깥 프로그램의 주소
https://example.com/translate를 입력하세요. - 활성화를 켜 두면 만든 즉시 요청을 보냅니다(ACTIVE). 잠시 시험만 하려면 꺼 두세요(INACTIVE).
- 생성 버튼을 눌러 Webhook을 만드세요.

목록에 새 상품 번역 알림이 ACTIVE 상태로 나타나면 Webhook이 만들어진 것입니다.

만든 뒤에는 옷가게에 실제로 새 상품을 하나 등록해 보세요. 등록하는 순간 Webhook이 적어 둔 주소로 요청을 보냅니다. 요청이 잘 갔는지, 바깥 프로그램이 어떻게 응답했는지는 Webhook의 호출 기록에서 확인할 수 있습니다.
켜고 끄기와 수정
Webhook은 만든 뒤에도 언제든 켜고 끌 수 있습니다. 잠시 요청을 멈추고 싶을 때는 삭제하지 말고 INACTIVE로 꺼 두세요. 꺼 둔 동안에는 새 상품을 등록해도 요청이 가지 않습니다. 다시 ACTIVE로 켜면 그때부터 다시 요청을 보냅니다.
만든 Webhook을 다시 열면 활성화를 끄거나 다시 켤 수 있습니다. 이름, 요청을 보낼 주소, 부를 변화 같은 내용도 나중에 수정할 수 있고, 더는 쓰지 않는 Webhook은 삭제하면 됩니다.
다음으로 할 일
- Content 모델링: Webhook이 요청을 부르는 대상인 "상품" 같은 Content의 양식 틀을 만드는 방법을 다룹니다.
- Content 작성하기: 실제 상품을 등록해 Webhook이 동작하는지 확인해 볼 수 있습니다.
- API 레퍼런스: Webhook을 프로그램에서 직접 만들고 관리할 때 쓰는 요청·응답 형식과 필드 명세를 다룹니다.
