Bulk Template Message API 문서 — v2
이 API를 사용하면 WhatsApp 채널을 통해 여러 수신자에게 템플릿 메시지를 동시에 보낼 수 있습니다.
메시지 발송은 비동기로 처리됩니다. API 요청은 즉시 bulkOperationId를 반환하고, 메시지는 대기열에 들어가 백그라운드에서 처리됩니다.
POST https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/v2/bulk-operation/send.js
이 문서의 모든 엔드포인트는 custom function입니다. HTTP 응답 본문에는 함수 자체의 필드(_id, title, pathString…)가 들어 있고, 엔드포인트의 실제 응답은 result 아래에 있습니다. 이 문서의 응답 예시는 result의 내용을 보여 줍니다.
{
"_id": "function_id",
"title": "Show",
"pathString": "/bulk-message-api-app/api/v2/bulk-operation/show.js",
"result": {
"bulkOperation": { "_id": "abc123xyz789", "status": "running" }
}
}
v1에서 마이그레이션
v2는 v1과 나란히 동작하므로 원하는 일정에 맞춰 마이그레이션할 수 있습니다. v1 엔드포인트(/api/bulk-message/…, /api/bulk-message-records/…)는 지원이 중단될 예정이며, 새로운 연동에는 v2를 사용해야 합니다.
- 엔드포인트 경로를 v2로 바꿉니다.
…/api/bulk-message/…는…/api/v2/bulk-operation/…이 되고,…/api/bulk-message-records/…는…/api/v2/bulk-operation-items/…가 됩니다.send요청 본문은 바뀌지 않으며, 이제 한 번의 요청에 5,000명이 아니라 최대 100,000명의 수신자를 담을 수 있습니다. - 요청에서
bulkMessageId대신bulkOperationId를 보냅니다. stats.js를show.js로 바꿉니다.- 아래 표의 새 필드 이름으로 응답을 읽습니다.
v2 엔드포인트는 v2 send로 생성된 대량 작업만 인식합니다. 전환하기 전에 v1으로 생성한 대량 메시지를 추적하려면 해당 메시지에는 계속 v1 엔드포인트를 사용하세요.
엔드포인트
| v1 | v2 | 응답에서 바뀌는 점 |
|---|---|---|
bulk-message/send.js | v2/bulk-operation/send.js | { bulkMessageId }가 { bulkOperationId }로 바뀝니다 |
bulk-message/stats.js | v2/bulk-operation/show.js | { bulkMessage, stats }가 { bulkOperation }으로 바뀝니다 |
bulk-message/message-stats.js | v2/bulk-operation/message-stats.js | 카운터가 messageStats 아래가 아니라 루트에 있습니다 |
bulk-message/stop.js | v2/bulk-operation/stop.js | 변경 없음 |
bulk-message-records/list.js | v2/bulk-operation-items/list.js | bulkMessageRecords가 bulkOperationItems로 바뀝니다 |
bulk-message-records/list-with-messages.js | v2/bulk-operation-items/list-with-messages.js | list와 같으며, 각 항목에 session도 포함됩니다 |
필드 이름
| v1 | v2 |
|---|---|
stats.success | bulkOperation.progress.succeeded |
stats.pending, stats.failed, stats.cancelled, stats.total | bulkOperation.progress.pending, .failed, .cancelled, .total |
| — | bulkOperation.progress.unknown, bulkOperation.status, bulkOperation.result.outcome |
bulkMessage(대량 메시지) | bulkOperation(대량 작업) |
bulkMessageRecord(레코드) | bulkOperationItem(항목) |
요청, send 응답, 레코드의 bulkMessageId | bulkOperationId |
보낸 그대로의 레코드 username / userId / sessionId | payload.username / payload.userId / payload.sessionId |
레코드 referenceId | payload.referenceId |
레코드 payload(수신자의 템플릿 페이로드) | payload.payload |
발송 후 레코드의 userId, sessionId, messageId | result.userId, result.sessionId, result.messageId |
레코드 상태 success | succeeded |
실패한 레코드의 errorMessage | error.message(error.code, error.retryable과 함께) |
| — | position(recipients에서 수신자의 인덱스) |
필터 {"status": "success"} | {"status": "succeeded"} |
필터 {"referenceId": "…"} | {"payload.referenceId": "…"} |
send 오류 schema-error, invalidRequest | zod-schema-error(메시지에 잘못된 필드의 경로 포함) |
주요 기능
- 한 번의 요청으로 1~100,000명의 수신자에게 대량 템플릿 메시지 발송
- 수신자별 개인화 변수(HEADER, BODY, BUTTONS, CAROUSEL)
- 모든 수신자에게 적용되는 공통
defaultPayload와 수신자별payload병합(isMerge) - 예약 발송(
scheduleAt) username(전화번호),userId,sessionId로 수신자 지정- 하나의 대량 요청 안에서 같은 사용자에게 여러 메시지 발송
- 외부 시스템과 레코드를 대조하기 위한 선택 항목
referenceId - 발송 후 대화를 사용자 또는 워크그룹에 자동 배정(
action)
이 API는 Meta의 승인을 받은 템플릿만 보낼 수 있습니다.
템플릿 이름과 언어 코드는 Meta Business Manager에 등록된 템플릿과 정확히 일치해야 합니다.
요청 파라미터
최상위 파라미터
| 파라미터 | 유형 | 필수 | 설명 |
|---|---|---|---|
channelUsername | string | 예 | 메시지를 보낼 WhatsApp 채널 번호(예: "908506669933") |
connectorBrand | string | 예 | 채널 커넥터 브랜드. 현재 지원하는 값: "whatsapp" |
title | string | 아니요 | 캠페인 제목. 대시보드에 표시됩니다. 기본값: "-" |
scheduleAt | string (ISO 8601) | 아니요 | 예약 발송 날짜와 시간(예: "2026-06-10T10:00:00.000Z"). 생략하면 메시지가 즉시 발송됩니다. |
defaultPayload | object | 조건부* | 모든 수신자에게 적용되는 기본 템플릿 페이로드. 수신자가 자체 payload를 제공하지 않으면 필수입니다. |
recipients | array | 예 | 수신자 목록. 최소 1명, 최대 100,000명. |
action | object | 아니요 | 발송 후 대화에 적용할 동작. assign-to-user 또는 assign-to-workgroup. |
*모든 수신자가 자체 payload를 제공하면 defaultPayload는 선택 사항입니다.
수신자 구조
수신자마다 username, userId, sessionId 중 정확히 하나를 입력해야 합니다. 여러 식별자를 함께 사용할 수 없으며, 모두 생략하면 오류가 발생합니다.
하나의 대량 요청 안에서 같은 사용자에게 여러 메시지를 보내려면 같은 식별자를 수신자 목록에 여러 번 추가하세요.
| 필드 | 유형 | 설명 |
|---|---|---|
username | string | 수신자의 전화번호(예: "905554443322"). 시스템에 없는 사용자는 자동으로 생성됩니다. |
userId | string | 시스템에 있는 기존 사용자의 ID. |
sessionId | string | 시스템에 있는 기존 대화 세션의 ID. |
referenceId | string | 외부 시스템 레코드(CRM, 주문, 티켓 등)와 대조하기 위한 선택 참조 ID(최대 256자). 입력하는 경우 대량 요청 안에서 고유해야 합니다. |
payload | object | 수신자별 템플릿 페이로드(선택). isMerge: true이면 defaultPayload와 병합됩니다. |
isMerge | boolean | 이 수신자에 대한 병합 동작을 재정의합니다(선택). 생략하면 defaultPayload.isMerge가 사용됩니다. |
Action 구조
| 필드 | 유형 | 설명 |
|---|---|---|
action.key | string | "assign-to-user"는 대화를 특정 상담원에게, "assign-to-workgroup"은 워크그룹에 배정합니다. |
action.value | string | 대상 사용자 또는 워크그룹의 ID. |
템플릿 페이로드 구조
defaultPayload와 수신자별 payload는 같은 구조를 사용합니다. 템플릿 메시지에는 type: "template"을 지정해야 합니다.
{
"type": "template",
"template": {
"templateName": "order_confirmation",
"languageCode": "tr-TR",
"variables": []
},
"isMerge": true
}
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
type | string | 예 | 페이로드 유형. 템플릿 메시지의 경우: "template" |
template.templateName | string | 예* | Meta에 등록된 템플릿 이름. isMerge: true를 사용하면 수신자 페이로드에서는 선택 사항입니다(기본값에서 상속). |
template.languageCode | string | 아니요 | 템플릿 언어 코드(예: "tr", "en", "tr-TR", "en-US"). Meta에 등록된 언어와 정확히 일치해야 합니다. |
template.variables | array | 아니요 | 템플릿 구성 요소에 적용할 변수 목록. 자세한 내용은 아래를 참고하세요. |
isMerge | boolean | 아니요 | true: 수신자 페이로드가 defaultPayload와 깊은 병합됩니다. false 또는 생략: 수신자 페이로드가 defaultPayload를 완전히 대체합니다. |
defaultPayload.isMerge: true이고 수신자가 자체payload를 제공하면 깊은 병합이 적용됩니다.- 깊은 병합에서는 수신자가
variables만 제공해도 되며,templateName과languageCode는 기본 페이로드에서 상속됩니다. - 수신자의
isMerge필드가defaultPayload.isMerge보다 우선합니다. - 수신자가
payload를 제공하지 않으면 항상defaultPayload가 사용됩니다.
변수 구조
variables 배열의 각 항목은 템플릿 구성 요소 하나에 해당합니다. 하나의 구성 요소 유형(type)은 배열에 한 번만 나타날 수 있습니다.
| type | 설명 | parameters | cards |
|---|---|---|---|
"HEADER" | 템플릿 헤더 구성 요소. 텍스트 변수 또는 미디어 URL(이미지, 동영상, 문서) | 예 | — |
"BODY" | 템플릿 본문 텍스트 변수 | 예 | — |
"BUTTONS" | 버튼의 동적 URL 접미사 변수 | 예 | — |
"CAROUSEL" | 캐러셀 템플릿의 카드 구성 요소 | — | 예 |
파라미터 형식
parameters 필드는 세 가지 형식으로 제공할 수 있습니다.
// Format 1: Array (index-based) — for placeholders like {{1}}, {{2}}, {{3}}
"parameters": ["value1", "value2", "value3"]
// Format 2: Object (numeric keys) — alternative representation for {{1}}, {{2}}
"parameters": { "1": "value1", "2": "value2" }
// Format 3: Object (named keys) — for placeholders like {{name}}, {{amount}}
"parameters": { "name": "John", "amount": "150 USD" }
- Meta 템플릿에서
{{1}},{{2}}같은 자리표시자를 사용한다면 배열 형식이나 숫자 키 객체 형식을 사용하세요. - 템플릿에서
{{firstName}}이나{{orderTotal}}같은 이름 있는 자리표시자를 사용한다면 이름 키 객체 형식을 사용하세요. - 배열을 사용할 때는 순서가 중요합니다. 첫 번째 요소는
{{1}}, 두 번째 요소는{{2}}에 대응하며, 이후도 마찬가지입니다.
HEADER 구성 요소
템플릿 헤더에 미디어(이미지, 동영상, 문서)나 동적 텍스트가 있을 때 HEADER 구성 요소를 사용합니다.
// Media header (image URL)
{
"type": "HEADER",
"parameters": ["https://example.com/product-image.jpg"]
}
// Text header variable
{
"type": "HEADER",
"parameters": ["Special Campaign"]
}
BODY 구성 요소
템플릿 본문의 동적 텍스트 변수에 사용합니다.
// Array format — when the template uses {{1}}, {{2}}, {{3}}
{
"type": "BODY",
"parameters": ["John", "ORD-12345", "150.00 USD"]
}
// Named format — when the template uses {{name}}, {{orderAmount}}
{
"type": "BODY",
"parameters": {
"name": "John",
"orderAmount": "150.00 USD"
}
}
BUTTONS 구성 요소
동적 URL 접미사가 있는 버튼에 사용합니다. 템플릿에 URL 버튼이 여러 개 있으면 변수가 있는 버튼에만 순서대로 인덱스가 매겨집니다(변수가 없는 버튼이나 FLOW 버튼은 무시됩니다).
// Single URL button suffix variable
{
"type": "BUTTONS",
"parameters": ["tracking/ORD-12345"]
}
// Multiple URL button suffix variables (first URL button, second URL button)
{
"type": "BUTTONS",
"parameters": ["tracking/ORD-12345", "cancel/ORD-12345"]
}
CAROUSEL 구성 요소
캐러셀 템플릿에서는 카드마다 구성 요소 변수를 따로 정의합니다.
{
"type": "CAROUSEL",
"cards": [
{
"components": [
{
"type": "HEADER",
"parameters": ["https://example.com/card1.jpg"]
},
{
"type": "BODY",
"parameters": ["Product A", "99.00 USD"]
}
]
},
{
"components": [
{
"type": "HEADER",
"parameters": ["https://example.com/card2.jpg"]
},
{
"type": "BODY",
"parameters": ["Product B", "149.00 USD"]
}
]
}
]
}
cards 배열의 길이는 템플릿에 정의된 카드 수와 정확히 일치해야 합니다.
예시
1. 간단한 템플릿 — 변수 없이 모든 수신자에게 같은 내용
변수가 없는 정적 템플릿을 여러 수신자에게 보내는 가장 간단한 방법입니다.
{
"channelUsername": "908506669933",
"connectorBrand": "whatsapp",
"title": "General Announcement",
"defaultPayload": {
"type": "template",
"template": {
"templateName": "hello_world",
"languageCode": "tr-TR"
}
},
"recipients": [{ "username": "905554443301" }, { "username": "905554443302" }, { "username": "905554443303" }]
}
curl --location 'https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/v2/bulk-operation/send.js' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--data '{
"channelUsername": "908506669933",
"connectorBrand": "whatsapp",
"title": "General Announcement",
"defaultPayload": {
"type": "template",
"template": {
"templateName": "hello_world",
"languageCode": "tr-TR"
}
},
"recipients": [
{ "username": "905554443301" },
{ "username": "905554443302" },
{ "username": "905554443303" }
]
}'
username으로 지정한 수신자가 시스템에 없으면 자동으로 생성됩니다- 모든 수신자가 같은
defaultPayload를 사용하므로 수신자별 데이터가 필요하지 않습니다 isMerge를 지정하지 않으면 각 수신자는defaultPayload를 그대로 사용합니다
2. 공통 본문 변수가 있는 템플릿
같은 변수 값으로 같은 템플릿을 모든 수신자에게 보냅니다. 템플릿에는 {{1}}, {{2}} 같은 자리표시자가 들어 있습니다.
{
"channelUsername": "908506669933",
"connectorBrand": "whatsapp",
"title": "Summer Campaign",
"defaultPayload": {
"type": "template",
"template": {
"templateName": "promo_notification",
"languageCode": "tr-TR",
"variables": [
{
"type": "BODY",
"parameters": ["30%", "July 31, 2026"]
}
]
}
},
"recipients": [{ "username": "905554443301" }, { "username": "905554443302" }]
}
- 이 예시에서 템플릿 본문은 다음과 같은 내용일 수 있습니다: "캠페인에서
{{1}}할인을 누리세요!{{2}}까지 유효합니다." parameters[0]은{{1}}을,parameters[1]은{{2}}를 대체합니다
3. 개인화 템플릿 — isMerge로 수신자별 변수 지정
수신자마다 다른 변수 값을 사용합니다. defaultPayload에 isMerge: true를 설정하면 수신자는 variables만 제공하면 되고, templateName과 languageCode는 기본 페이로드에서 상속됩니다.
이 예시의 템플릿에는 미디어 헤더와 개인화된 본문 변수가 모두 포함되어 있습니다.
{
"channelUsername": "908506669933",
"connectorBrand": "whatsapp",
"title": "Personal Order Notification",
"defaultPayload": {
"type": "template",
"template": {
"templateName": "order_confirmation",
"languageCode": "tr-TR"
},
"isMerge": true
},
"recipients": [
{
"username": "905554443301",
"payload": {
"template": {
"variables": [
{
"type": "HEADER",
"parameters": ["https://s3.example.com/product-a.jpg"]
},
{
"type": "BODY",
"parameters": ["Ahmet", "ORD-001", "150.00 TRY"]
}
]
}
}
},
{
"username": "905554443302",
"payload": {
"template": {
"variables": [
{
"type": "HEADER",
"parameters": ["https://s3.example.com/product-b.jpg"]
},
{
"type": "BODY",
"parameters": ["Fatma", "ORD-002", "289.00 TRY"]
}
]
}
}
}
]
}
isMerge: true덕분에 수신자 페이로드마다templateName을 반복할 필요가 없습니다- 각 수신자의
variables배열은 완전히 독립적입니다 - 헤더 미디어 URL은 공개적으로 접근할 수 있어야 합니다
- 수신자 페이로드에서
type을 생략하면defaultPayload.type이 상속됩니다
4. URL 버튼 변수
템플릿에 동적 URL 접미사가 있는 버튼이 있을 때 BUTTONS 구성 요소를 사용합니다. 예를 들어 기본 URL https://example.com/track/ 뒤에 운송장 번호를 붙이는 경우입니다.
{
"channelUsername": "908506669933",
"connectorBrand": "whatsapp",
"title": "Shipment Tracking Notification",
"defaultPayload": {
"type": "template",
"template": {
"templateName": "shipment_tracking",
"languageCode": "tr-TR"
},
"isMerge": true
},
"recipients": [
{
"username": "905554443301",
"payload": {
"template": {
"variables": [
{
"type": "BODY",
"parameters": ["Ahmet", "TK-98765"]
},
{
"type": "BUTTONS",
"parameters": ["TK-98765"]
}
]
}
}
},
{
"username": "905554443302",
"payload": {
"template": {
"variables": [
{
"type": "BODY",
"parameters": ["Recep", "TK-12345"]
},
{
"type": "BUTTONS",
"parameters": ["TK-12345"]
}
]
}
}
}
]
}
BUTTONS파라미터는 변수가 있는 버튼의 순서에 따라 대응됩니다- FLOW 버튼과 변수가 없는 버튼은 무시되며, 인덱스는 동적 URL 버튼에만 적용됩니다
- 템플릿 버튼 URL이
https://example.com/track/{{1}}이면parameters[0]이{{1}}을 대체합니다
5. 예약 발송
미래의 날짜와 시간에 메시지를 보내도록 예약합니다. scheduleAt은 ISO 8601 UTC 타임스탬프로 입력해야 합니다.
{
"channelUsername": "908506669933",
"connectorBrand": "whatsapp",
"title": "Morning Campaign Message",
"scheduleAt": "2026-06-10T07:00:00.000Z",
"defaultPayload": {
"type": "template",
"template": {
"templateName": "morning_campaign",
"languageCode": "en-US"
},
"isMerge": true
},
"action": {
"key": "assign-to-workgroup",
"value": "WORKGROUP_ID"
},
"recipients": [
{
"username": "905554443301",
"payload": {
"template": {
"variables": [
{
"type": "BODY",
"parameters": { "name": "Ahmet", "discountRate": "25" }
}
]
}
}
},
{
"username": "905554443302",
"payload": {
"template": {
"variables": [
{
"type": "BODY",
"parameters": { "name": "Zeynep", "discountRate": "40" }
}
]
}
}
}
]
}
scheduleAt은 UTC 시간대로 입력해야 합니다(예: 튀르키예 시간 오전 10:00이면07:00:00.000Z사용)- 이 예시는 이름 있는 파라미터 형식을 사용하므로 템플릿에
{{name}},{{discountRate}}같은 자리표시자가 있어야 합니다 action을 사용하면 메시지 발송으로 생성된 대화가 지정한 워크그룹에 자동으로 배정됩니다
응답
요청이 성공하면 발송 진행 상황을 추적하는 데 사용할 수 있는 bulkOperationId가 반환됩니다.
{
"bulkOperationId": "abc123xyz789"
}
오류 응답
| 상태 | 설명 |
|---|---|
recipients field is required | recipients 필드를 보내지 않았습니다 |
Channel not found | channelUsername, connectorBrand와 일치하는 인증된 채널을 찾을 수 없습니다 |
Channel is not verified | 일치하는 채널은 있지만 아직 인증되지 않았습니다 |
zod-schema-error | 요청 본문의 필드가 스키마 검증에 실패했습니다(필수 필드 누락, 잘못된 유형, 지원하지 않는 값, 수신자 한 명에 둘 이상의 식별자, 또는 payload 누락). 메시지에 해당 필드가 표시됩니다. 예: recipients.0.referenceId: referenceId must be unique |
반환된 bulkOperationId는 아래 엔드포인트에서 발송 통계를 조회하거나 발송을 중단하는 데 사용할 수 있습니다.
Show — 대량 작업 상태 및 통계
대량 작업과 현재 상태, 항목의 발송 카운터를 bulkOperation 키 아래에서 조회합니다.
POST https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/v2/bulk-operation/show.js
요청 파라미터
| 파라미터 | 유형 | 필수 | 설명 |
|---|---|---|---|
bulkOperationId | string | 예 | 추적할 대량 작업의 ID(send에서 반환됨) |
{
"bulkOperationId": "abc123xyz789"
}
curl --location 'https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/v2/bulk-operation/show.js' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--data '{
"bulkOperationId": "abc123xyz789"
}'
응답
{
"bulkOperation": {
"_id": "abc123xyz789",
"type": "send-bulk-message",
"slug": "my-workspace",
"userId": "agent_user_id",
"payload": {
"title": "Campaign Title",
"channelUsername": "908506669933",
"connectorBrand": "whatsapp",
"connectorSlug": "om2WabVegaCloud",
"defaultLanguageCode": "tr-TR",
"sentUser": { "firstname": "John", "lastname": "Doe" },
"defaultPayload": {
"type": "template",
"template": { "templateName": "order_confirmation", "languageCode": "tr-TR" },
"isMerge": true
},
"source": { "type": "recipients", "bulkOperationInputGroupId": "group_id" }
},
"status": "running",
"result": { "outcome": "unknown", "hasErrors": false },
"progress": {
"total": 100,
"pending": 45,
"succeeded": 42,
"failed": 3,
"cancelled": 10,
"unknown": 0
},
"scheduleAt": "2026-06-10T07:00:00.000Z",
"createdAt": "2026-06-08T10:00:00.000Z",
"updatedAt": "2026-06-10T07:00:30.000Z",
"startedAt": "2026-06-10T07:00:05.000Z"
}
}
Show 필드
필드는 bulkOperation 아래에 있습니다.
| 필드 | 유형 | 설명 |
|---|---|---|
status | string | 대량 작업 상태: preparing, scheduled, pending, running, stopping, stopped, completed, failed |
progress.total | number | 전체 항목 수 |
progress.pending | number | 아직 대기열에서 기다리는 항목 수 |
progress.succeeded | number | 발송 처리에 성공한 항목 수 |
progress.failed | number | 발송 오류가 발생한 항목 수 |
progress.cancelled | number | stop으로 취소된 항목 수 |
progress.unknown | number | 결과를 확인할 수 없는 항목 수 |
result.outcome | string | 대량 작업이 끝난 뒤의 최종 결과: success, partial, all-failed, cancelled, unknown |
result.hasErrors | boolean | 실패한 항목이 하나 이상 있으면 true |
scheduleAt | string | 예약 발송 시간(예약된 대량 작업에만 해당) |
startedAt, completedAt | string | 발송이 시작되고 끝난 시간 |
progress.succeeded는 시스템이 메시지를 성공적으로 전달 처리했다는 뜻입니다. Meta가 메시지를 수신자에게 전달했음을 보장하는 것은 아닙니다. Meta가 이후에 생성하는 상태 업데이트(delivered, read, failed 등)는 이 카운터에 영향을 주지 않으며 여기에 반영되지 않습니다.
- show 엔드포인트: 항목의 발송 상태(
pending,succeeded,failed,cancelled,unknown)를 보여 줍니다 - message-stats 엔드포인트: 실제 메시지 상태(
pending,sending,sent,delivered,read,error등)를 보여 줍니다 - 항목이
succeeded여도 메시지가 아직 전달되지 않았거나 읽히지 않았을 수 있습니다
참조 ID
referenceId는 추적을 위해 발송 항목을 자체 시스템의 ID(예: 주문 번호, 티켓 ID)와 대조할 때 사용하는 선택 필드입니다. 각 항목에서 payload.referenceId로 반환됩니다.
입력하는 경우 대량 요청 안에서 고유해야 합니다(최대 256자).
- 반드시 사용해야 하는 것은 아닙니다
- 같은 대량 작업 안에서는 중복될 수 없습니다
- 서로 다른 대량 작업에서는 다시 사용할 수 있습니다
Message Stats — WhatsApp 메시지 상태
대량 작업의 메시지 상태 통계를 조회합니다. show 엔드포인트와 달리 항목 상태가 아니라 발송된 메시지의 실제 message.status를 보여 줍니다.
가능한 메시지 상태: pending, sending, sent, delivered, read, deleted, warning, error, unknown, not-sended
POST https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/v2/bulk-operation/message-stats.js
요청 파라미터
| 파라미터 | 유형 | 필수 | 설명 |
|---|---|---|---|
bulkOperationId | string | 예 | 조회할 대량 작업의 ID |
{
"bulkOperationId": "abc123xyz789"
}
curl --location 'https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/v2/bulk-operation/message-stats.js' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--data '{
"bulkOperationId": "abc123xyz789"
}'
응답
{
"pending": 5,
"sending": 2,
"sent": 10,
"delivered": 45,
"read": 30,
"deleted": 2,
"warning": 1,
"error": 3,
"unknown": 0,
"not-sended": 2,
"no-message": 20,
"total": 120
}
Message Stats 필드
| 필드 | 유형 | 설명 |
|---|---|---|
pending | number | 아직 발송되지 않은 메시지 |
sending | number | 현재 발송 중인 메시지 |
sent | number | WhatsApp에 넘겨졌지만 아직 전달되지 않은 메시지 |
delivered | number | 수신자의 휴대전화에 전달된 메시지 |
read | number | 수신자가 읽은 메시지 |
deleted | number | 삭제된 메시지 |
warning | number | 경고가 있는 메시지 |
error | number | 오류가 있는 메시지 |
unknown | number | 상태를 알 수 없는 메시지 |
not-sended | number | 발송할 수 없었던 메시지 |
no-message | number | 메시지가 없는 항목(아직 대기 중이거나 실패 또는 취소됨) |
total | number | 전체 항목 수 |
- 대량 작업이
running인 동안no-message수가 많으면 발송이 아직 진행 중이라는 뜻입니다 delivered수는 메시지가 성공적으로 전달된 건수를 보여 줍니다read수로 캠페인 참여도를 측정할 수 있습니다- 이 엔드포인트를 주기적으로 호출해 실시간으로 추적할 수 있습니다
Options 구조 — 페이지 나누기, 필터링, 정렬
list와 list-with-messages 엔드포인트에서 사용하는 options 파라미터는 페이지 나누기, 필터링, 정렬을 위한 표준 구조를 제공합니다.
Options 필드
| 필드 | 유형 | 필수 | 설명 |
|---|---|---|---|
pagination | object | 아니요 | 페이지 나누기 설정 |
filtering | object | 아니요 | 필터 조건 |
sorting | object | 아니요 | 정렬 설정(예: {"position": 1}) |
Pagination 구조
pagination을 보내지 않으면 첫 페이지가 최대 50개 항목으로 반환됩니다. 보내는 경우 currentPage는 필수입니다.
| 필드 | 유형 | 기본값 | 설명 |
|---|---|---|---|
currentPage | number | — | 조회할 페이지 번호(pagination을 보낼 때 필수) |
pageItems | number | 50 | 페이지당 항목 수(최대 50) |
Filtering 구조
필터링은 항목 필드에 MongoDB 쿼리 형식을 사용하며, 필드 이름은 응답에 나오는 그대로 사용합니다(예: status, payload.referenceId, result.messageId). list-with-messages 엔드포인트에서는 메시지 필드(예: message.status)로도 필터링할 수 있습니다.
// Filter by item status
{
"options": {
"filtering": {
"status": "succeeded"
}
}
}
// Filter by referenceId
{
"options": {
"filtering": {
"payload.referenceId": "ORDER-12345"
}
}
}
// Filter by message status (only list-with-messages)
{
"options": {
"filtering": {
"message.status": "delivered"
}
}
}
// Filter by multiple statuses using $in (MongoDB operator)
{
"options": {
"filtering": {
"message.status": {
"$in": ["delivered", "read"]
}
}
}
}
// Exclude specific statuses using $nin (MongoDB operator)
{
"options": {
"filtering": {
"message.status": {
"$nin": ["error", "not-sended"]
}
}
}
}
// List items that have no message (only list-with-messages)
{
"options": {
"filtering": {
"message": {
"$exists": false
}
}
}
}
Sorting 구조
정렬은 MongoDB 정렬 형식을 사용합니다. 1은 오름차순, -1은 내림차순입니다. position은 요청의 recipients 배열에서 수신자의 순서입니다(0부터 시작).
// Sort in the order the recipients were sent in the request
{
"options": {
"sorting": {
"position": 1
}
}
}
// Sort by multiple fields
{
"options": {
"sorting": {
"status": 1,
"position": 1
}
}
}
조합 사용 예시
{
"bulkOperationId": "abc123xyz789",
"options": {
"pagination": {
"currentPage": 2,
"pageItems": 50
},
"filtering": {
"message.status": "delivered"
},
"sorting": {
"position": 1
}
}
}
이 예시는 전달된 메시지의 두 번째 페이지를 수신자 순서대로 나열합니다.
filtering과pagination을 함께 사용해 원하는 대상만 조회하세요- 응답에는 페이지 이동에 쓸 수 있는
options.pagination.totalCount와options.pagination.totalPages가 포함됩니다 message.status필터는list-with-messages엔드포인트에서만 동작합니다
Items — 항목 상세 및 목록
대량 작업의 모든 발송 항목을 나열합니다. 각 항목은 recipients 배열의 요소 하나를 나타냅니다.
List — 항목 목록
POST https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/v2/bulk-operation-items/list.js
요청 파라미터
| 파라미터 | 유형 | 필수 | 설명 |
|---|---|---|---|
bulkOperationId | string | 예 | 나열할 대량 작업의 ID |
options | object | 아니요 | 페이지 나누기, 필터링, 정렬 옵션. 자세한 내용은 Options 구조를 참고하세요. |
요청 — 기본 사용법
{
"bulkOperationId": "abc123xyz789",
"options": {
"pagination": {
"currentPage": 1,
"pageItems": 50
}
}
}
요청 — 특정 상태로 필터링
{
"bulkOperationId": "abc123xyz789",
"options": {
"pagination": {
"currentPage": 1,
"pageItems": 50
},
"filtering": {
"status": "failed"
}
}
}
실패한 발송 항목만 error에 담긴 사유와 함께 나열합니다.
응답
{
"bulkOperationItems": [
{
"_id": "item_1",
"bulkOperationId": "abc123xyz789",
"bulkOperationBatchId": "batch_1",
"slug": "my-workspace",
"position": 0,
"payload": {
"username": "905554443301",
"referenceId": "ORDER-12345",
"payload": {
"template": {
"variables": [{ "type": "BODY", "parameters": ["Ahmet", "ORD-001"] }]
}
}
},
"status": "succeeded",
"attempt": 1,
"result": {
"userId": "user_123",
"sessionId": "session_456",
"messageId": "msg_789"
},
"createdAt": "2026-06-10T10:00:00.000Z",
"updatedAt": "2026-06-10T10:00:01.000Z",
"completedAt": "2026-06-10T10:00:01.000Z"
},
{
"_id": "item_2",
"bulkOperationId": "abc123xyz789",
"bulkOperationBatchId": "batch_1",
"slug": "my-workspace",
"position": 1,
"payload": { "username": "905554443302", "referenceId": "ORDER-12346" },
"status": "failed",
"attempt": 1,
"error": { "code": "error", "message": "Variable missing", "retryable": false },
"result": { "userId": "user_124", "sessionId": "session_457" },
"createdAt": "2026-06-10T10:00:00.000Z",
"updatedAt": "2026-06-10T10:00:01.000Z",
"completedAt": "2026-06-10T10:00:01.000Z"
}
],
"options": {
"pagination": {
"currentPage": 1,
"pageItems": 50,
"totalCount": 150,
"totalPages": 3
},
"sorting": {},
"filtering": {},
"searching": {}
}
}
List With Messages — 메시지 상세 포함 목록
list 엔드포인트와 같은 기능(페이지 나누기, 필터링, 정렬)을 제공합니다. 차이점은 관련 message와 session 객체가 있으면 각 항목에 함께 포함된다는 것입니다. 덕분에 메시지 상태(delivered, read, error 등)와 페이로드를 한 번의 쿼리로 확인할 수 있습니다.
POST https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/v2/bulk-operation-items/list-with-messages.js
요청 — 기본 사용법
{
"bulkOperationId": "abc123xyz789",
"options": {
"pagination": {
"currentPage": 1,
"pageItems": 50
}
}
}
요청 — 메시지 상태로 필터링
{
"bulkOperationId": "abc123xyz789",
"options": {
"pagination": {
"currentPage": 1,
"pageItems": 50
},
"filtering": {
"message.status": "delivered"
}
}
}
이 예시는 상태가 "delivered"인 메시지만 가져옵니다.
요청 — 실패한 메시지 나열
{
"bulkOperationId": "abc123xyz789",
"options": {
"pagination": {
"currentPage": 1,
"pageItems": 50
},
"filtering": {
"message.status": "error"
}
}
}
발송 오류가 발생한 메시지를 검토할 때 사용합니다.
응답
{
"bulkOperationItems": [
{
"_id": "item_1",
"bulkOperationId": "abc123xyz789",
"position": 0,
"payload": { "username": "905554443301", "referenceId": "ORDER-12345" },
"status": "succeeded",
"result": {
"userId": "user_123",
"sessionId": "session_456",
"messageId": "msg_789"
},
"message": {
"_id": "msg_789",
"type": "template",
"status": "delivered",
"payload": {
"template": {
"name": "order_notification"
}
},
"createdAt": "2026-06-10T10:01:00.000Z"
},
"session": {
"_id": "session_456",
"status": "opened"
}
}
],
"options": {
"pagination": {
"currentPage": 1,
"pageItems": 50,
"totalCount": 150,
"totalPages": 3
},
"sorting": {},
"filtering": {},
"searching": {}
}
}
항목 필드
| 필드 | 유형 | 설명 |
|---|---|---|
_id | string | 항목 ID |
bulkOperationId | string | 이 항목이 속한 대량 작업 ID |
position | number | 요청의 recipients 배열에서 수신자의 순서(0부터 시작) |
payload | object | 요청에서 보낸 그대로의 수신자(username / userId / sessionId, referenceId, payload, isMerge) |
status | string | 항목 상태: pending, succeeded, failed, cancelled, unknown |
error | object | failed 항목에만 있음: code, message, retryable |
result.userId | string | 수신자 사용자 ID |
result.sessionId | string | 대화 세션 ID |
result.messageId | string | 발송된 메시지 ID(메시지가 발송되지 않았으면 없음) |
completedAt | string | 항목이 처리된 시간 |
message | object | 메시지 상세(list-with-messages에만 있음) |
session | object | 대화 상세(list-with-messages에만 있음) |
List와 List-With-Messages 비교
| 기능 | list | list-with-messages |
|---|---|---|
| 페이지 나누기 | 예 | 예 |
| 필터링 | 예(항목 필드만) | 예(항목 + 메시지 필드) |
| Message / Session | 아니요 | 예(있는 경우) |
| 성능 | 빠름 | 보통(JOIN 수행) |
| message.status 필터 | 사용 불가 | 사용 가능 |
- list: 모든 항목을 빠르게 나열, referenceId로 필터링, 항목 상태 확인
- list-with-messages: WhatsApp 메시지 상태(delivered, read) 확인, 메시지 페이로드 검토
- 필터링 예시:
{"status": "failed"}— 발송에 실패한 항목{"payload.referenceId": "ORDER-123"}— 특정 참조 ID{"message.status": "delivered"}— 전달된 메시지(list-with-messages에서만){"message.status": "read"}— 읽은 메시지(list-with-messages에서만){"message.status": "error"}— 오류가 있는 메시지(list-with-messages에서만){"message.status": {"$in": ["delivered", "read"]}}— 전달되었거나 읽은 메시지(list-with-messages에서만){"message.status": {"$nin": ["error", "not-sended"]}}— 오류 및 미발송을 제외한 메시지(list-with-messages에서만){"message": {"$exists": false}}— 메시지가 없는 항목(list-with-messages에서만){"status": {"$in": ["succeeded", "pending"]}}— 성공했거나 대기 중인 항목
list엔드포인트가 더 빠르므로 일반적인 상태 확인에는 이 엔드포인트를 우선 사용하세요- 메시지 상세가 필요할 때
list-with-messages를 사용하세요 - 페이지당 최대 항목 수(pageItems)는 50이며, 지정하지 않으면 기본값은 50입니다
message.status필터는list-with-messages에서만 동작합니다result.messageId가 없는 항목에는message객체가 없습니다
Stop — 발송 취소
진행 중인 대량 작업 발송을 중단합니다. 모든 pending 항목은 cancelled 상태로 바뀌고 대량 작업은 stopped가 됩니다. 이미 succeeded 또는 failed로 처리된 항목은 영향을 받지 않습니다. 예약된 대량 작업도 scheduleAt 시간 전에 중단할 수 있습니다.
POST https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/v2/bulk-operation/stop.js
이 작업은 되돌릴 수 없습니다. 중단된 발송은 다시 시작할 수 없습니다.
요청 파라미터
| 파라미터 | 유형 | 필수 | 설명 |
|---|---|---|---|
bulkOperationId | string | 예 | 중단할 대량 작업의 ID |
{
"bulkOperationId": "abc123xyz789"
}
curl --location 'https://app.monochat.ai/api/:slug/custom-functions/bulk-message-api-app/api/v2/bulk-operation/stop.js' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--data '{
"bulkOperationId": "abc123xyz789"
}'
응답
요청이 성공하면 응답 본문은 비어 있습니다. HTTP 200 상태는 작업이 성공적으로 완료되었음을 나타냅니다.
{}
- 이 API로 생성된 대량 작업만 중단할 수 있습니다
- 발송을 중단한 뒤
show엔드포인트로 취소된 항목 수(bulkOperation.progress.cancelled)를 확인할 수 있습니다 - 대량 작업을 찾을 수 없으면 API가 다음 오류를 반환합니다:
Bulk operation not found.