제로콜
개발자 문서B2B API

B2B API

ZeroCall B2B API를 통해 사용자 관리, 그룹/채널 관리, 통화 기록 조회, 메시지 발송, 연락처/CRM 관리, 사업자 등록 등의 기능을 외부 시스템과 연동할 수 있습니다.

Base URL

https://b2b-gateway.zerocall.kr/api/v1

Rate Limit

1,200 requests / 60초
항목값
인증 방식API Key (x-api-key 헤더)
Content-Typeapplication/json
문자 인코딩UTF-8

공통 응답 형식

B2B API는 정상 응답이 기본적으로code,message,result형태로 감싸져 반환됩니다.

에러 응답은 interceptor 래핑 없이status,code,message형식으로 반환됩니다.

json
{
  "code": "0",
  "message": "Success",
  "result": {
    "externalUserId": "user_12345"
  }
}
json
{
  "status": 404,
  "code": "UR001",
  "message": "존재하지 않는 사용자에요!"
}

아래 섹션의 Response 예시는 가독성을 위해 대부분 result 내부 payload만 표시합니다.

인증

모든 API 요청에는 x-api-key 헤더가 필요합니다. API Key는 B2B 파트너 계정에서 발급받을 수 있습니다.

API Key 형식

http
x-api-key: b2b_xxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

온보딩 URL 생성

사용자가 ZeroCall 서비스에 가입할 수 있는 온보딩 URL을 생성합니다. 생성된 URL을 통해 사용자가 가입을 완료하면 자동으로 B2B 클라이언트에 연결됩니다.

POST/auth/onboarding-url

Request Body

필드타입필수설명
externalUserIdstring선택고객사의 사용자 ID (가입 완료 시 매핑됨, 최대 255자)
namestring선택토큰 이름 (관리용)
expiresAtstring선택만료일시 ISO 8601 (기본: 30일 후)
maxUsesnumber선택최대 사용 횟수 (미입력 시 무제한, 1~100,000)
skipAppRedirectboolean선택온보딩 완료 후 앱 설치 화면으로 이동하지 않음 (기본: false)
json
Request
{
  "externalUserId": "user_12345",
  "name": "마케팅 캠페인 A",
  "expiresAt": "2026-05-01T00:00:00.000Z",
  "maxUses": 100,
  "skipAppRedirect": true
}
Response
{
  "onboardingUrl": "https://app.zerocall.kr/auth/b2b-onboarding?token=...",
  "expiresAt": "2026-05-01T00:00:00.000Z"
}

온보딩 URL 목록 조회

생성한 온보딩 URL 목록을 조회합니다.

GET/auth/onboarding-url
json
Response
{
  "urls": [
    {
      "tokenId": 1,
      "name": "마케팅 캠페인 A",
      "onboardingUrl": "https://app.zerocall.kr/auth/b2b-onboarding?token=...",
      "expiresAt": "2026-05-01T00:00:00.000Z",
      "maxUses": 100,
      "currentUses": 5,
      "isActive": true,
      "isValid": true,
      "createdAt": "2026-04-01T00:00:00.000Z"
    }
  ]
}

Response Fields

필드타입설명
tokenIdnumber토큰 ID
namestring | null토큰 이름
onboardingUrlstring온보딩 URL
expiresAtstring만료일시 (ISO 8601)
maxUsesnumber | null최대 사용 횟수 (null: 무제한)
currentUsesnumber현재 사용 횟수
isActiveboolean활성 상태
isValidboolean유효 여부 (활성 + 미만료 + 사용 횟수 미초과)
createdAtstring생성일시 (ISO 8601)

온보딩 URL 비활성화

온보딩 URL을 비활성화합니다. 비활성화된 URL은 더 이상 사용할 수 없습니다.

DELETE/auth/onboarding-url/:tokenId
bash
curl -X DELETE "https://b2b-gateway.zerocall.kr/api/v1/auth/onboarding-url/1" \
  -H "x-api-key: b2b_xxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

본인인증 URL 생성

사용자 본인인증을 위한 URL을 생성합니다.

POST/auth/identity-verification-url

Request Body

필드타입필수설명
externalUserIdstring필수고객사의 사용자 ID
json
Request
{
  "externalUserId": "user_12345"
}
Response
{
  "verificationUrl": "https://verify.zerocall.kr/...",
  "expiresAt": "2026-04-17T15:30:00.000Z"
}
예외 코드설명
UR001존재하지 않는 사용자
UR006이미 본인인증 완료

사용자 관리

고객사의 사용자를 ZeroCall과 연동합니다.

사용자 목록 조회

GET/user
json
Response
[
  {
    "externalUserId": "user_12345",
    "name": "홍길동",
    "userPhoneNumber": "01012345678",
    "birthday": "900101"
  }
]

사용자 생성

POST/user

isIdentityVerified 값에 따라 필수 필드가 달라집니다.

Request Body

필드타입필수설명
externalUserIdstring필수고객사의 사용자 ID
isIdentityVerifiedboolean선택본인인증 완료 여부 (기본값: false)
namestring조건부사용자 이름 (isIdentityVerified=true일 때 필수)
phoneNumberstring조건부전화번호 010XXXXXXXX (isIdentityVerified=true일 때 필수)
birthdaystring조건부생년월일 yyMMdd (isIdentityVerified=true일 때 필수)

Case 1: 본인인증 미완료 사용자 (isIdentityVerified=false)

사용자를 먼저 생성하고, 이후 본인인증 URL을 통해 인증을 진행합니다.

json
Request
{
  "externalUserId": "user_12345"
}
Response
{
  "externalUserId": "user_12345"
}

Case 2: 본인인증 완료 사용자 (isIdentityVerified=true)

이미 고객사에서 본인인증을 완료한 사용자를 등록합니다. name, phoneNumber, birthday가 필수입니다.

사전 협의 필요: 본인인증 완료 상태로 사용자를 등록하려면 ZeroCall과 사전 협의가 필요합니다.

json
Request
{
  "externalUserId": "user_12345",
  "isIdentityVerified": true,
  "name": "홍길동",
  "phoneNumber": "01012345678",
  "birthday": "900101"
}
Response
{
  "externalUserId": "user_12345"
}
예외 코드설명
UR007중복 사용자 검토 필요
UR008이미 등록된 유저
BB001지원되지 않는 고객사
BB008자체 본인인증 미허용 클라이언트

사용자 상세 조회

GET/user/detail?externalUserId=user_12345
json
Response
{
  "externalUserId": "user_12345",
  "name": "홍길동",
  "userPhoneNumber": "01012345678",
  "birthday": "900101"
}
예외 코드설명
UR001존재하지 않는 사용자

본인인증 여부 조회

GET/user/identity-verification-status?externalUserId=user_12345
json
Response (개인 사용자)
{
  "isIdentityVerified": true,
  "isCorporate": false
}
Response (법인 사용자)
{
  "isIdentityVerified": true,
  "isCorporate": true,
  "corporateVerificationStatus": "ACTIVE"
}
예외 코드설명
UR001존재하지 않는 사용자

corporateVerificationStatus 값

법인 사용자(isCorporate=true)인 경우에만 반환됩니다.

값설명
PENDING검수 대기
ACTIVE승인됨
REJECTED거절됨

사용자 삭제

DELETE/user?externalUserId=user_12345
json
Response
{
  "success": true
}
예외 코드설명
UR001존재하지 않는 사용자
UR003그룹을 모두 삭제해야 사용자 삭제 가능

결제

분산형 과금에서 사용자 크레딧과 결제수단 등록 URL을 관리합니다.

지정 사용자 결제수단 등록/관리 URL 생성

POST/payment/user/payment-method-url?externalUserId=user_12345

요청 시 1일 동안 유효한 결제수단 등록 세션 토큰을 생성합니다. 토큰은 응답 URL의tokenquery에 포함됩니다.

json
Response
{
  "paymentMethodUrl": "https://app.zerocall.kr/auth/b2b-payment-method?token=...",
  "expiresAt": "2026-04-17T15:00:00.000Z"
}
예외 코드설명
UR001존재하지 않는 사용자
BB001지원되지 않는 고객사

지정 사용자 결제수단 목록 조회

GET/payment/user/payment-method?externalUserId=user_12345

사용자가 등록한 결제수단 목록을 조회합니다. 카드 만료일이 지났거나 만료된 상태인 경우 status가 EXPIRED로 반환됩니다.

json
Response
[
  {
    "id": 1001,
    "cardNumber": "1234-****-****-5678",
    "cardCompany": "신한카드",
    "cardCompanyCode": "SHINHAN",
    "expireDateTime": "2026-12-31T23:59:59.000Z",
    "isPrimary": true,
    "status": "ACTIVE"
  }
]

status 값

  • ACTIVE - 정상
  • EXPIRED - 만료됨
예외 코드설명
UR001존재하지 않는 사용자

분산형 사용자 크레딧 현황 조회

GET/payment/user/credit?externalUserId=user_12345

사용자 자동충전 설정 수정 API는 제공하지 않습니다. 결제수단 등록/관리 URL을 통해 사용자가 직접 관리합니다.

추가 결제 API

MethodPath설명
GET/payment/user/credit-history사용자별 B2B 서비스 사용 크레딧 이력 조회
GET/payment/client/creditB2B client 크레딧 현황 조회
GET/payment/client/credit-historyB2B client 전체 크레딧 소모 이력 조회

그룹 관리

그룹은 전화번호와 연결된 서비스 단위입니다.

그룹 목록 조회

GET/group?externalUserId=user_12345
json
Response
[
  {
    "id": 1,
    "paymentPlan": "BASIC",
    "billingModel": "PREPAID",
    "title": "서울 지점",
    "isOwner": true,
    "phoneNumber": "07012345678",
    "memberList": [
      {
        "id": 10,
        "externalUserId": "user_12345",
        "isOwner": true,
        "name": "홍길동",
        "phoneNumber": "01012345678",
        "isPending": false,
        "isCallReceiveEnabled": true
      },
      {
        "id": 11,
        "externalUserId": null,
        "isOwner": false,
        "name": null,
        "phoneNumber": "01098765432",
        "isPending": true,
        "isCallReceiveEnabled": true
      }
    ],
    "routeToAi": true,
    "status": "ACTIVE",
    "isAiAvailable": true,
    "nextPaymentPlan": null,
    "handoffEnabled": true,
    "recordingEnabled": true,
    "useSMSTool": true,
    "useCRMTool": false,
    "business": {
      "id": 1,
      "companyName": "ABC 회사",
      "businessNumber": "1234567890"
    },
    "ownerHasPaymentMethod": true
  }
]

status 값 (GroupStatus)

값설명
ACTIVE활성화
INACTIVE비활성화

memberList 필드

필드타입설명
idnumber그룹 멤버 ID
externalUserIdstring | nullB2B 고객사 사용자 ID. 초대 중이거나 매핑되지 않은 경우 null
isOwnerboolean그룹장 여부
namestring | null사용자 이름. 초대 중인 미가입 사용자는 null
phoneNumberstring멤버 전화번호
isPendingboolean초대 중인 미가입 사용자 여부
isCallReceiveEnabledboolean전화 수신 가능 여부

그룹 생성

POST/group?externalUserId=user_12345

Request Body

필드타입필수설명
titlestring필수그룹명 (최대 80자)
phoneNumberstring선택전화번호 (미입력 시 자동 발급)
planstring선택요금제 (ECHO, BASIC, BUSINESS, PRO)
json
Request
{
  "title": "서울 지점",
  "phoneNumber": "07012345678"
}
Response
{
  "id": 1
}
예외 코드설명
UR005본인인증 필요
GR011비활성화 그룹 존재
GR012복수 그룹 생성 시 결제수단 필요
CH003이미 사용중이거나 존재하지 않는 전화번호 또는 이의제기 불가 통화기록
CH004전화번호가 다른 요청에 의해 처리 중

그룹 상세 조회

GET/group/{groupId}?externalUserId=user_12345

응답 형식은 그룹 목록 조회의 단일 객체와 동일합니다.

그룹 수정

PATCH/group/{groupId}?externalUserId=user_12345

Request Body

필드타입필수설명
titlestring선택그룹명 (최대 80자)
routeToAiboolean선택AI 응대 활성화 여부
handoffEnabledboolean선택담당자 연결 기능 활성화 여부
recordingEnabledboolean선택통화 녹음 활성화 여부
useSMSToolboolean선택SMS tool 활성화 여부
useCRMToolboolean선택CRM tool 활성화 여부
handoffDisabledMessagestring선택담당자 연결 비활성화 시 안내 메시지 (최대 500자)
json
Request
{
  "title": "서울 지점 (수정)",
  "handoffEnabled": true,
  "recordingEnabled": true
}
Response
{
  "id": 1
}
예외 코드설명
GR001존재하지 않는 그룹
GR004현재 요금제에서 AI 기능 사용 불가

그룹 삭제

DELETE/group/{groupId}?externalUserId=user_12345

성공 시 200 OK (빈 응답)

예외 코드설명
GR001존재하지 않는 그룹
GR008미납 요금 존재로 그룹 삭제 불가
GR009결제 수단 미등록으로 그룹 삭제 불가
GR010그룹 삭제 결제 실패

착신전환 설정 조회

GET/group/{groupId}/call-forward?externalUserId=user_12345
json
Response
{
  "isCallForwardEnabled": true,
  "callForwardNumber": "01098765432"
}
예외 코드설명
GR001존재하지 않는 그룹

착신전환 설정 수정

PATCH/group/{groupId}/call-forward?externalUserId=user_12345

Request Body

필드타입필수설명
isCallForwardEnabledboolean필수착신전환 활성화 여부
callForwardNumberstring조건부착신전환 전화번호 (활성화 시 필수)
json
Request (착신전환 활성화)
{
  "isCallForwardEnabled": true,
  "callForwardNumber": "01098765432"
}
Request (착신전환 비활성화)
{
  "isCallForwardEnabled": false
}
Response: 200 OK (빈 응답)
예외 코드설명
GR001존재하지 않는 그룹
GR007착신전환 활성화 시 전화번호 필요

추가 그룹 API

MethodPath설명
POST/group/{groupId}/member멤버 추가
DELETE/group/{groupId}/member/{groupMemberId}멤버 제거
POST/group/{groupId}/leave그룹 탈퇴
PATCH/group/{groupId}/member/{groupMemberId}/call-receive그룹원 전화 수신 여부 수정
GET/group/plans그룹 생성 가능 요금제 목록 조회
GET/group/{groupId}/ai-usage그룹 AI 사용량 조회
GET/group/{groupId}/integration-config외부 API 연동 설정 조회
PATCH/group/{groupId}/integration-config외부 API 연동 설정 수정
GET/group/{groupId}/certificate통신서비스 이용증명원 PDF 발급

전화번호 풀

그룹이나 채널 생성 시 발급할 수 있는 전화번호 샘플을 조회합니다.

발급 가능한 전화번호 조회

GET/phone-number-pool/available
json
Response
{
  "isAvailable": true,
  "phoneNumber": "07012345678"
}
Response (발급 가능한 번호가 없는 경우)
{
  "isAvailable": false
}

채널 관리

채널은 그룹 내의 개별 전화번호입니다.

채널 목록 조회

GET/channel?externalUserId=user_12345&groupId=1
json
Response
[
  {
    "id": 1,
    "type": "PHONE",
    "phoneNumber": "07012345678",
    "groupId": 1,
    "isPrimary": true
  }
]

채널 생성

POST/channel?externalUserId=user_12345

Request Body

필드타입필수설명
groupIdnumber필수제로콜 내부 그룹 ID
typestring선택채널 타입 (PHONE, 기본값)
phoneNumberstring선택전화번호 (미입력 시 자동 발급)
json
Request
{
  "groupId": 1,
  "type": "PHONE",
  "phoneNumber": "07012345678"
}
Response
{
  "id": 1
}
예외 코드설명
CH003이미 사용중이거나 존재하지 않는 전화번호 또는 이의제기 불가 통화기록
CH004전화번호가 다른 요청에 의해 처리 중
CH006추가 번호 발급에 결제수단 필요
GL004처리할 수 없는 상태
BB006해당 사용자에 접근 권한 없음
BB005해당 그룹에 접근 권한 없음

기본번호 설정

PATCH/channel/{channelId}/primary?externalUserId=user_12345&groupId=1
json
Response
{
  "success": true
}
예외 코드설명
CH001존재하지 않는 채널/통화기록

채널 삭제

DELETE/channel/{channelId}?externalUserId=user_12345
json
Response
{
  "id": 1
}
예외 코드설명
CH001존재하지 않는 채널/통화기록

통화 기록

그룹의 통화 기록을 조회하고 관리합니다.

통화 기록 조회

GET/call-history?externalUserId=...&groupId=1

Query Parameters

파라미터타입필수설명
externalUserIdstring필수사용자 ID
groupIdnumber필수그룹 ID
cursorstring선택페이지네이션 커서
sizenumber선택조회 개수 (기본: 20, 최대: 100)
searchstring선택전화번호 또는 연락처 이름 검색
channelIdnumber선택특정 채널 필터
json
Response
{
  "data": [
    {
      "id": "uuid-123",
      "from": "01012345678",
      "to": "07012345678",
      "isInbound": true,
      "status": "ANSWERED",
      "tag": "AI_TO_DIRECT",
      "duration": 120,
      "historySummary": "예약 문의 후 담당자 연결",
      "sttAIList": [
        { "role": "agent", "text": "안녕하세요.", "timestamp": 1.2 }
      ],
      "sttDirectList": [
        { "role": "customer", "text": "예약 변경하고 싶어요.", "timestamp": 30.5 }
      ],
      "channelId": 1,
      "createdAt": "2026-04-17T14:00:00.000Z",
      "isRecorded": true
    }
  ],
  "nextCursor": "eyJpZCI6MTIzfQ==",
  "hasNext": true
}
예외 코드설명
AU007권한 없음

cursor 포맷이 잘못되면 HTTP 400 BadRequestException이 반환됩니다.

녹음 파일 URL 조회

GET/call-history/{callHistoryId}/recording?externalUserId=user_12345

Query Parameters

파라미터타입필수설명
externalUserIdstring필수사용자 ID
json
Response
{
  "callHistoryId": "uuid-123",
  "recordings": [
    {
      "agentType": "direct",
      "recordingTarget": "all",
      "url": "https://storage.example.com/recording.wav?...",
      "expiresAt": "2026-04-17T15:00:00.000Z",
      "contentLength": 1234567,
      "contentType": "audio/wav",
      "supportsRange": true
    }
  ]
}
예외 코드설명
CH001존재하지 않는 채널/통화기록
AU007권한 없음

녹음 파일 URL은 5분 후 만료됩니다. 파일을 보관해야 하는 경우, URL 수신 후 즉시 다운로드하세요.

통화기록 이의 제기

POST/call-history/{callHistoryId}/claim?externalUserId=...
json
Request
{
  "claimReason": "실제 통화 결과와 태그가 달라 재검토 요청"
}
Response: 200 OK (빈 응답)
예외 코드설명
CH001존재하지 않는 채널/통화기록
CH003이미 사용중이거나 존재하지 않는 전화번호 또는 이의제기 불가 통화기록
AU007권한 없음

미확인 부재중 전화 개수 조회

GET/call-history/unread-count?externalUserId=...&groupId=1
json
Response
{
  "count": 3
}
예외 코드설명
AU007권한 없음

모든 미확인 통화기록 읽음 처리

PATCH/call-history/read-all?externalUserId=...&groupId=1
json
Response: 200 OK (빈 응답)
예외 코드설명
AU007권한 없음

status 값 (CallStatus)

값설명
ANSWERED응답됨
MISSED부재중
REJECTED거절됨
UNKNOWN알 수 없음

tag 값 (CallTag)

값설명
AIAI 응대
DIRECT직원 응대
AI_TO_DIRECTAI에서 직원으로 전환
UNKNOWN알 수 없음

solveTag 값 (SolveTag)

값설명
SOLVED해결됨
UNSOLVED미해결
ASSESSING검토 중

메시지 발송

SMS/LMS/MMS 메시지를 발송합니다.

채팅방 목록 조회

GET/chat-room?externalUserId=...&groupId=1

Query Parameters

파라미터타입필수설명
externalUserIdstring필수사용자 ID
groupIdnumber필수그룹 ID
cursorstring선택페이지네이션 커서
sizenumber선택조회 개수 (기본: 50, 최대: 200)
json
Response
{
  "data": [
    {
      "id": "chatroom_abc123",
      "lastMessageContent": "안녕하세요. 예약 확인 부탁드립니다.",
      "lastMessageAt": "2026-04-17T14:30:00.000Z",
      "channelId": 1,
      "channelType": "PHONE",
      "channelPhoneNumber": "07012345678",
      "guestPhoneNumber": "01098765432",
      "contactId": 123,
      "contactTitle": "홍길동",
      "sequence": 15,
      "operatorReadSequence": 15,
      "guestReadSequence": 10,
      "groupId": 1,
      "isChannelRemoved": false
    }
  ],
  "nextCursor": "eyJpZCI6MTIzfQ==",
  "hasNext": true
}
예외 코드설명
AU007권한 없음

cursor 포맷이 잘못되면 HTTP 400 BadRequestException이 반환됩니다.

채팅방 상세 조회

GET/chat-room/{chatRoomId}?externalUserId=...

응답 형식은 채팅방 목록 조회의 단일 객체와 동일합니다.

예외 코드설명
CR001존재하지 않는 채팅방

채팅방 생성

POST/chat-room?externalUserId=...

Request Body

필드타입필수설명
channelIdnumber필수채널 ID
phoneNumberstring필수상대 전화번호
json
Request
{
  "channelId": 1,
  "phoneNumber": "01098765432"
}
Response
"chatroom_abc123"
예외 코드설명
CH001존재하지 않는 채널/통화기록

채팅방 나가기

DELETE/chat-room/{chatRoomId}?externalUserId=...
json
Response
"chatroom_abc123"
예외 코드설명
CR001존재하지 않는 채팅방

메시지 발송

POST/chat?externalUserId=...

채팅 메시지 ID(chatId)는 32자, 채팅방 ID(chatRoomId)는 30자 랜덤 문자열입니다.

응답의 sendStatus는 ZeroCall이 메시지 사업자에게 발송 요청을 넘기는 데 성공했는지 여부입니다. 실제 최종 전달 성공/실패는 이후 SEND_RESULT 웹훅의resultStatus로 판단하세요.

Request Body

필드타입필수설명
chatRoomIdstring필수채팅방 ID (30자)
contentstring필수메시지 본문 (최대 2000자). 이미지와 함께 보내는 MMS에서는 빈 문자열도 허용됩니다.
imageListstring[]선택MMS 첨부 목록. 미디어 업로드 API가 돌려준 S3 key 또는 접근 가능한 URL 문자열 배열
externalMessageIdstring선택고객사 추적용 외부 메시지 ID. SEND_RESULT 웹훅에도 그대로 포함될 수 있습니다.
subjectstring선택제목 (최대 64자). 있으면 LMS/MMS로 발송됩니다.

메시지 타입 판정

조건결과
`imageList`에 값이 1개 이상 있음MMS
`subject`가 없고 본문이 90바이트 이하SMS
그 외LMS
json
Request
{
  "chatRoomId": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
  "content": "안녕하세요. 예약이 확정되었습니다.",
  "subject": "예약 확정 안내",
  "externalMessageId": "ext_msg_001"
}
Response: ZeroCall -> 메시지 사업자 요청 성공 (HTTP 200)
{
  "chatId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "b2bClientId": 1,
  "chatRoomId": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
  "groupId": 1,
  "channelId": 1,
  "content": "안녕하세요. 예약이 확정되었습니다.",
  "subject": "예약 확정 안내",
  "imageList": [],
  "externalMessageId": "ext_msg_001",
  "sendPhoneNumber": "07012345678",
  "recvPhoneNumber": "01098765432",
  "sendStatus": "SUCCESS",
  "chatType": "LMS",
  "createdAt": "2026-04-17T10:00:00.000Z"
Response: ZeroCall -> 메시지 사업자 요청 실패 (HTTP 200)
{
  "chatId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "b2bClientId": 1,
  "chatRoomId": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
  "groupId": 1,
  "channelId": 1,
  "content": "안녕하세요. 예약이 확정되었습니다.",
  "imageList": [],
  "sendPhoneNumber": "07012345678",
  "recvPhoneNumber": "01098765432",
  "sendStatus": "FAILED",
  "createdAt": "2026-04-17T10:00:00.000Z"
}

Response 필드

필드타입설명
chatIdstring메시지 ID (32자)
b2bClientIdnumberB2B 클라이언트 ID
chatRoomIdstring채팅방 ID (30자)
groupIdnumber그룹 ID
channelIdnumber채널 ID
contentstring저장된 메시지 본문
subjectstring제목. 없으면 응답에서 생략될 수 있음
imageListstring[]저장된 첨부 목록
externalMessageIdstring요청 시 보낸 외부 메시지 ID. 없으면 생략될 수 있음
sendPhoneNumberstring발신 번호
recvPhoneNumberstring수신 번호
sendStatusSUCCESS | FAILED메시지 사업자 요청 성공 여부
chatTypeSMS | LMS | MMS | RCS | KAOZeroCall이 판정한 메시지 타입. 즉시 실패 시 생략될 수 있음
resultStatusstring최종 전달 결과 코드. 즉시 응답에는 보통 없음
resultMessagestring최종 전달 결과 메시지. 즉시 응답에는 보통 없음
createdAtstring생성 시각 (ISO 8601)

주의

sendStatus: SUCCESS는 최종 전달 성공을 의미하지 않습니다. 최종 성공 여부는 웹훅의resultStatus가4100,6600,7000 중 하나인지로 판단하세요.

예외 코드설명
CR001존재하지 않는 채팅방

채팅 대화 조회

GET/chat?externalUserId=...&chatRoomId=chatroom_abc123

Query Parameters

파라미터타입필수설명
externalUserIdstring필수사용자 ID
chatRoomIdstring필수채팅방 ID
cursorDatestring선택이 시점 이전 메시지 조회 (ISO)
sizenumber선택조회 개수 (최대 200)
json
Response
[
  {
    "id": "chat_001",
    "channelId": 1,
    "chatRoomId": "chatroom_abc123",
    "groupId": 1,
    "sequence": 12,
    "content": "예약은 3시로 도와드릴게요.",
    "senderType": "OPERATOR",
    "timestamp": "2026-04-17T14:30:00.000Z"
  }
]
예외 코드설명
CR001존재하지 않는 채팅방

미디어 업로드

POST/chat/media/upload?externalUserId=...

MMS 발송을 위한 이미지를 업로드합니다. 최대 3개 파일까지 가능합니다.

bash
curl -X POST "https://b2b-gateway.zerocall.kr/api/v1/chat/media/upload?externalUserId=user_12345" \
  -H "x-api-key: b2b_xxxx_..." \
  -F "files=@image1.jpg" \
  -F "files=@image2.jpg"

# Response
{
  "urls": [
    "chat-media/123/1713355800000-ab12cd.jpg",
    "chat-media/123/1713355800000-ef34gh.jpg"
  ]
}
예외 코드설명
BB006해당 사용자에 접근 권한 없음

010 메시지 발송

본인인증이 완료된 사용자의 휴대폰 번호를 기준으로 메시지를 발송하고, 결과를 조회합니다.

인증된 휴대폰으로 메시지 발송

본인인증이 완료된 사용자의 인증된 휴대폰 번호로 메시지를 발송합니다.

POST/verified-phone-chat

Request Body

필드타입필수설명
externalUserIdstring필수고객사의 사용자 ID
externalMessageIdstring선택고객사 관리용 외부 메시지 ID
toPhoneNumberstring필수수신자 전화번호
contentstring필수메시지 내용
imageListstring[]선택이미지 URL 목록
subjectstring선택제목
json
Request
{
  "externalUserId": "user_12345",
  "externalMessageId": "ext_msg_001",
  "toPhoneNumber": "01098765432",
  "content": "안녕하세요. 예약이 확정되었습니다."
}
Response
{
  "id": "msg_abc123def456",
  "b2bClientId": 1,
  "content": "안녕하세요. 예약이 확정되었습니다.",
  "imageList": [],
  "fromPhoneNumber": "01012345678",
  "toPhoneNumber": "01098765432",
  "sendStatus": "SUCCESS",
  "chatType": "SMS",
  "resultStatus": "4100",
  "resultMessage": "전달",
  "createdAt": "2026-04-17T10:00:00.000Z"
}
예외 코드설명
BB006해당 사용자에 접근 권한 없음
UR005본인인증 필요

인증 휴대폰 메시지 발송 결과 조회

GET/verified-phone-chat?externalUserId=user_12345

Query Parameters

파라미터타입필수설명
externalUserIdstring필수고객사 사용자 ID
externalMessageIdstring선택발송 시 전달한 외부 메시지 ID
cursorDatestring선택이 날짜 이전 메시지부터 조회 (ISO)
sizenumber선택조회 개수 (최대 200)
json
Response
[
  {
    "id": "msg_abc123def456",
    "b2bClientId": 1,
    "content": "안녕하세요. 예약이 확정되었습니다.",
    "subject": null,
    "imageList": [],
    "externalMessageId": "ext_msg_001",
    "fromPhoneNumber": "01012345678",
    "toPhoneNumber": "01098765432",
    "sendStatus": "SUCCESS",
    "chatType": "SMS",
    "resultStatus": "4100",
    "resultMessage": "전달",
    "createdAt": "2026-04-17T10:00:00.000Z"
  }
]
예외 코드설명
BB006해당 사용자에 접근 권한 없음
UR005본인인증 필요

sendStatus 값

값설명
SUCCESS발송 성공
FAILED발송 실패

chatType 값

값설명
SMS단문 메시지
LMS장문 메시지
MMS멀티미디어 메시지
RCSRCS 메시지
KAO카카오 알림톡

인증 휴대폰 메시지 미디어 업로드

POST/verified-phone-chat/media/upload?externalUserId=user_12345

인증 휴대폰 메시지(MMS) 발송용 이미지 업로드 API입니다. `files` 필드로 최대 3개까지 업로드할 수 있습니다.

Multipart Form Data

필드타입필수설명
externalUserIdstring필수Query Parameter
filesfile[]필수첨부 파일 배열 (최대 3개)
bash
curl -X POST "https://b2b-gateway.zerocall.kr/api/v1/verified-phone-chat/media/upload?externalUserId=user_12345" \
  -H "x-api-key: b2b_xxxx_..." \
  -F "files=@image1.jpg" \
  -F "files=@image2.jpg"

# Response
{
  "urls": [
    "chat-media/verified/abc123.jpg",
    "chat-media/verified/def456.jpg"
  ]
}
예외 코드설명
BB006해당 사용자에 접근 권한 없음

연락처/CRM

연락처를 관리하고 CRM 커스텀 속성을 정의합니다.

연락처 목록 조회

GET/contact?externalUserId=...&groupId=1
json
Response
{
  "data": [
    {
      "id": 1,
      "createdAt": "2026-04-17T10:00:00.000Z",
      "title": "홍길동",
      "phoneNumber": "01012345678",
      "memo": "VIP 고객",
      "customData": {
        "등급": "Gold",
        "가입일": "2025-01-01"
      }
    }
  ],
  "nextCursor": "...",
  "hasNext": true
}
예외 코드설명
CT004현재 요금제에서 CRM 기능 사용 불가
GR001존재하지 않는 그룹

cursor 포맷이 잘못되면 HTTP 400 BadRequestException이 반환됩니다.

연락처 추가

POST/contact?externalUserId=...

Request Body

필드타입필수설명
groupIdnumber필수그룹 ID
titlestring필수연락처 이름 (최대 80자)
phoneNumberstring필수전화번호 (숫자만)
memostring선택메모 (최대 500자)
customDataobject선택CRM 커스텀 속성 데이터 (key: propertyId)
json
Request
{
  "groupId": 1,
  "title": "홍길동",
  "phoneNumber": "01012345678",
  "memo": "VIP 고객",
  "customData": {
    "1": "Gold",
    "2": "2025-01-01"
  }
}
Response
{
  "id": 123
}
예외 코드설명
CT003커스텀 데이터 오류
CT004현재 요금제에서 CRM 기능 사용 불가
CT008이미 존재하는 연락처
GR001존재하지 않는 그룹

연락처 일괄 추가

POST/contact/bulk?externalUserId=...

Request Body

필드타입필수설명
groupIdnumber필수그룹 ID
contactsarray필수연락처 배열 (최소 1개, 최대 100개)
contacts[].titlestring필수연락처 이름 (최대 80자)
contacts[].phoneNumberstring필수전화번호 (숫자만)
contacts[].memostring선택메모 (최대 500자)
json
Request
{
  "groupId": 1,
  "contacts": [
    {
      "title": "홍길동",
      "phoneNumber": "01012345678",
      "memo": "신규 고객"
    },
    {
      "title": "김철수",
      "phoneNumber": "01087654321"
    }
  ]
}
Response
[
  101,
  102
]

CRM 스키마 관리

연락처의 커스텀 속성 스키마를 정의합니다.

GET/contact/crm/schema?externalUserId=...&groupId=1스키마 조회
POST/contact/crm/schema?externalUserId=...스키마 생성
PATCH/contact/crm/schema/{id}?externalUserId=...스키마 수정
DELETE/contact/crm/schema/{id}?externalUserId=...스키마 삭제

스키마 생성 Request Body

필드타입필수설명
groupIdnumber필수그룹 ID
titlestring필수속성명 (최대 80자)
propertyTypestring필수속성 타입 (아래 표 참조)
descriptionstring선택설명 (최대 255자)
isRequiredboolean선택필수 여부 (기본: false)
displayOrdernumber선택표시 순서 (기본: 0)
listOptionsarray선택LIST 타입일 때 선택지 목록
defaultValueany선택기본값
aiAccessstring선택AI 접근 권한 (NONE, READ, WRITE)

스키마 수정(PATCH)은 groupId 없이 수정할 필드만 전달합니다.

json
// 스키마 조회 Response
[
  {
    "id": 1,
    "title": "등급",
    "propertyType": "LIST",
    "description": "고객 등급",
    "isRequired": false,
    "displayOrder": 0,
    "listOptions": [
      { "label": "Gold", "value": "gold" },
      { "label": "Silver", "value": "silver" }
    ],
    "defaultValue": null,
    "aiAccess": "READ"
  }
]

// 스키마 생성 Request
{
  "groupId": 1,
  "title": "가입일",
  "propertyType": "DATE",
  "description": "서비스 가입일",
  "aiAccess": "READ"
}

// 스키마 생성 Response
{
  "id": 2,
  "title": "가입일",
  "propertyType": "DATE",
  "description": "서비스 가입일",
  "isRequired": false,
  "displayOrder": 0,
  "listOptions": null,
  "defaultValue": null,
  "aiAccess": "READ",
  "createdAt": "2026-04-17T16:00:00+09:00",
  "updatedAt": "2026-04-17T16:00:00+09:00"
}
예외 코드설명
CT002커스텀 속성 정의 오류
CT004현재 요금제에서 CRM 기능 사용 불가
CT006존재하지 않는 커스텀 속성
GR001존재하지 않는 그룹

CRM 속성 타입 (ContactCustomPropertyType)

타입설명
STRING문자열
NUMBER숫자
DATE날짜
EMAIL이메일
BOOLEAN참/거짓
LIST목록 (선택지)
CONTACT연락처 참조

AI 접근 권한 (ContactCustomPropertyAiAccess)

값설명
NONEAI 접근 불가
READAI 읽기 전용
WRITEAI 읽기/쓰기

추가 연락처/CRM API

MethodPath설명
GET/contact/{contactId}연락처 상세 조회
POST/contact/search고급 필터 조회 (customFilters)
PATCH/contact/{contactId}연락처 수정
PATCH/contact/{contactId}/custom-data커스텀 데이터 set/unset
DELETE/contact/{contactId}연락처 삭제
PUT/contact/crm/schema/bulk스키마 일괄 저장(upsert + 누락 삭제)

연락처 파일 가져오기

POST/contact/crm/import/file?externalUserId=...

CSV 또는 XLSX 파일로 연락처를 일괄 가져옵니다.

bash
curl -X POST "https://b2b-gateway.zerocall.kr/api/v1/contact/crm/import/file?externalUserId=user_12345" \
  -H "x-api-key: b2b_xxxx_..." \
  -F "groupId=1" \
  -F "file=@contacts.xlsx"

# Response
{
  "totalRows": 100,
  "createdCount": 98,
  "rejectedCount": 2,
  "createdPropertyList": [],
  "rejectedRows": [
    {
      "rowNumber": 15,
      "reason": "잘못된 전화번호 형식",
      "rawPhoneNumber": "010-0000-0000",
      "sourceRow": { "phoneNumber": "010-0000-0000" }
    }
  ],
  "requiredDefaultFilledCount": 1,
  "requiredDefaultFilledRows": [
    {
      "rowNumber": 42,
      "propertyId": 7,
      "propertyTitle": "고객등급"
    }
  ]
}
예외 코드설명
CT004현재 요금제에서 CRM 기능 사용 불가
CT007CRM 가져오기 요청 오류
GR001존재하지 않는 그룹

사업자 관리

B2B client 소속 사업자를 등록하고 그룹과 연결합니다. 사업자 정보는 별도 business 리소스로 저장되며, 현재 API key의 B2B client에 연결된 사업자만 조회됩니다.

사업자 목록 조회

GET/business

현재 B2B client에 등록된 사업자 목록을 조회합니다. 다른 B2B client의 사업자나 일반 사용자 소유 사업자는 포함되지 않습니다.

json
Response
[
  {
    "id": 1,
    "businessType": "CORPORATE",
    "businessNumber": "1234567890",
    "corporateNumber": "1234567890123",
    "companyName": "ABC 회사",
    "representativeName": "홍길동",
    "businessCategory": "서비스업",
    "businessItems": "소프트웨어 개발",
    "address": "서울시 강남구",
    "detailedAddress": "테헤란로 123",
    "registrationCertificateUrl": "https://...",
    "registrantType": "REPRESENTATIVE",
    "authorizationDocumentUrl": null,
    "verificationStatus": "ACTIVE",
    "groupCount": 2,
    "linkedGroups": [
      {
        "id": 1,
        "title": "서울 지점",
        "phoneNumber": "07012345678"
      }
    ]
  }
]
예외 코드설명
BS001사업자 정보 없음

verificationStatus 값 (BusinessVerificationStatus)

값설명
PENDING검수 대기
ACTIVE승인됨
REJECTED거절됨

사업자 등록

POST/business

Request Body

필드타입필수설명
businessTypestring선택INDIVIDUAL, CORPORATE
businessNumberstring필수사업자등록번호 (10자리)
corporateNumberstring선택법인/주민등록번호 (13자리)
companyNamestring필수상호 (최대 100자)
representativeNamestring필수대표자명 (최대 50자)
businessCategorystring선택업태
businessItemsstring선택종목
addressstring선택주소
registrationCertificateUrlstring필수사업자등록증 파일 URL
registrantTypestring선택REPRESENTATIVE, EMPLOYEE, AGENT
authorizationDocumentUrlstring조건부위임장 (EMPLOYEE/AGENT일 때 필수)
json
Request
{
  "businessNumber": "1234567890",
  "companyName": "ABC 회사",
  "representativeName": "홍길동",
  "businessCategory": "서비스업",
  "registrationCertificateUrl": "business/cert_123.pdf"
}
Response
{
  "id": 1
}
예외 코드설명
BS003이미 등록된 사업자등록번호
BS004직원/대리인 위임장(재직증명) 필요

businessType 값 (BusinessType)

값설명
INDIVIDUAL개인사업자
CORPORATE법인사업자

registrantType 값 (RegistrantType)

값설명
REPRESENTATIVE대표자
EMPLOYEE직원
AGENT대리인

사업자등록증 업로드

POST/business/upload/registration-certificate

지원 형식: PNG, JPG, JPEG, PDF, TIF, TIFF

json
Response
{
  "key": "business/cert_abc123.pdf"
}
예외 코드설명
BS018파일 누락
BS019지원하지 않는 파일 형식
BS020파일 크기 초과

여러 그룹에 사업자 일괄 연결

POST/business/{businessId}/bulk-link

Request Body

json
Request
{
  "groupIds": [1, 2, 3]
}
Response
{
  "linkedCount": 3,
  "pendingCount": 0,
  "skippedCount": 0,
  "message": "3개 그룹이 연결되었습니다"
}
예외 코드설명
BS001사업자 정보 없음
BS005검증 완료된 사업자만 연결 가능

추가 사업자 API

MethodPath설명
GET/business/{id}사업자 상세 조회
PATCH/business/{id}사업자 정보 수정
DELETE/business/{id}사업자 삭제
POST/business/{id}/transfer사업자 관리 양도
GET/business/{id}/my-owned-groups연결 가능한 그룹 목록 조회
DELETE/business/group/{groupId}그룹에서 사업자 연결 해제
GET/business/search/by-number/{businessNumber}사업자등록번호로 검색
GET/business/{id}/link-history연결 이력 조회
GET/business/{id}/usage-certificate이용 증명원 데이터 조회

매뉴얼 관리

AI 응대에 사용되는 매뉴얼과 매장 정보를 관리합니다.

매뉴얼 목록 조회

GET/manual?externalUserId=...&groupId=1
json
Response
[
  {
    "id": 1,
    "title": "예약 안내",
    "instruction": "예약 문의 시 가능한 날짜와 시간을 안내해주세요.",
    "groupId": 1,
    "isActive": true,
    "type": null,
    "isDefault": false
  },
  {
    "id": 2,
    "title": "영업시간 안내",
    "instruction": "영업시간은 09:00~18:00입니다.",
    "groupId": 1,
    "isActive": true,
    "type": null,
    "isDefault": false
  }
]
예외 코드설명
AU007권한 없음

매뉴얼 추가

POST/manual?externalUserId=...

Request Body

필드타입필수설명
groupIdnumber필수그룹 ID
titlestring필수정책명 (최대 80자)
instructionstring필수지시사항 (최대 1500자)
isActiveboolean선택활성화 여부 (기본: true)
json
Request
{
  "groupId": 1,
  "title": "예약 안내",
  "instruction": "예약 문의 시 가능한 날짜와 시간을 안내해주세요.",
  "isActive": true
}
Response
{
  "id": 1
}
예외 코드설명
AU007권한 없음

매뉴얼 수정

PATCH/manual/{id}?externalUserId=...
json
Request
{
  "title": "예약 안내 (수정)",
  "instruction": "예약 문의 시 가능한 날짜와 시간, 인원수를 확인해주세요.",
  "isActive": true
}
Response: 200 OK (빈 응답)
예외 코드설명
MA001존재하지 않는 매뉴얼

매뉴얼 삭제

DELETE/manual/{id}?externalUserId=...
json
Response: 200 OK (빈 응답)
예외 코드설명
MA001존재하지 않는 매뉴얼

매장 정보 조회

GET/manual/store-info?externalUserId=...&groupId=1
json
Response
{
  "groupId": 1,
  "address": "서울시 강남구 테헤란로 123",
  "operationHours": "평일 09:00~18:00, 주말 휴무",
  "basicInfo": "주차 2시간 무료"
}
예외 코드설명
AU007권한 없음

매장 정보 등록/수정

PATCH/manual/store-info?externalUserId=...

Request Body

필드타입필수설명
groupIdnumber필수그룹 ID
addressstring선택매장 주소 (최대 1500자)
operationHoursstring선택운영시간 (최대 1500자)
basicInfostring선택기본 정보 (최대 150자)
bankInfostring선택계좌 정보 (최대 500자)
wifiInfostring선택와이파이 안내 (최대 500자)
parkingInfostring선택주차 안내 (최대 500자)
json
Request
{
  "groupId": 1,
  "address": "서울시 강남구 테헤란로 123",
  "operationHours": "평일 09:00~18:00, 주말 휴무",
  "basicInfo": "예약제로 운영되는 강남점입니다.",
  "bankInfo": "국민은행 123456-78-901234 제로콜",
  "wifiInfo": "SSID: ZeroCall_Guest / PW: zerocall1234",
  "parkingInfo": "건물 지하주차장 2시간 무료"
}
Response
{
  "groupId": 1,
  "address": "서울시 강남구 테헤란로 123",
  "operationHours": "평일 09:00~18:00, 주말 휴무",
  "basicInfo": "예약제로 운영되는 강남점입니다.",
  "bankInfo": "국민은행 123456-78-901234 제로콜",
  "wifiInfo": "SSID: ZeroCall_Guest / PW: zerocall1234",
  "parkingInfo": "건물 지하주차장 2시간 무료"
}
예외 코드설명
AU007권한 없음

AI 비활성화 시간 설정

특정 시간대에 AI 응대를 비활성화합니다. 시간은 자정(00:00)부터의 분 단위로 입력합니다.

설정 목록 조회

GET/pass-time-setting?externalUserId=...&groupId=1
json
Response
[
  {
    "id": 1,
    "groupId": 1,
    "startMinute": 1080,
    "endMinute": 540,
    "repeatDays": ["MON", "TUE", "WED", "THU", "FRI"],
    "isActive": true,
    "createdAt": "2026-04-17T10:00:00.000Z",
    "updatedAt": "2026-04-17T10:00:00.000Z"
  }
]

// 참고: startMinute=1080 → 18:00, endMinute=540 → 09:00
예외 코드설명
AU007권한 없음

설정 추가

POST/pass-time-setting?externalUserId=...

Request Body

필드타입필수설명
groupIdnumber필수그룹 ID
startMinutenumber필수시작 시간 (0-1439, 자정부터 분 단위)
endMinutenumber필수종료 시간 (0-1439, 자정부터 분 단위)
repeatDaysstring[]선택반복 요일 (MON, TUE, WED, THU, FRI, SAT, SUN)
isActiveboolean선택활성화 여부 (기본: true)
json
Request (18:00~익일 09:00 평일 설정)
{
  "groupId": 1,
  "startMinute": 1080,
  "endMinute": 540,
  "repeatDays": ["MON", "TUE", "WED", "THU", "FRI"],
  "isActive": true
}
Response
{
  "id": 1
}
예외 코드설명
AU007권한 없음

시간 변환 예시

시간분 값
00:000
09:00540
12:00720
18:001080
23:591439

설정 수정

PATCH/pass-time-setting/{id}?externalUserId=...
json
Request
{
  "isActive": false
}
Response: 200 OK (빈 응답)
예외 코드설명
PT001존재하지 않는 설정
AU007권한 없음

설정 상세 조회

GET/pass-time-setting/{id}?externalUserId=...

응답 형식은 설정 목록 조회의 단일 객체와 동일합니다.

예외 코드설명
PT001존재하지 않는 설정
AU007권한 없음

설정 삭제

DELETE/pass-time-setting/{id}?externalUserId=...
json
Response: 200 OK (빈 응답)
예외 코드설명
PT001존재하지 않는 설정
AU007권한 없음

웹훅

이벤트 발생 시 등록된 URL로 HTTP POST 요청을 전송합니다.

이벤트설명
MESSAGE_RECEIVED고객으로부터 메시지 수신
SEND_RESULT메시지 발송 결과 수신
CALL_RECEIVED고객으로부터 수신 전화 유입

현재 B2B API의 웹훅 consumer 구현 기준으로 `MESSAGE_RECEIVED`, `SEND_RESULT`, `CALL_RECEIVED` 이벤트를 제공합니다.

공통 Payload 형식

모든 B2B 웹훅은 동일한 envelope 구조를 사용하며, 이벤트별 상세 필드는 `data` 내부에 담깁니다.

typescript
type WebhookEnvelope<T> = {
  eventType: 'MESSAGE_RECEIVED' | 'SEND_RESULT' | 'CALL_RECEIVED';
  timestamp: string; // ISO 8601
  data: T;
};

// MESSAGE_RECEIVED
interface B2bMessageReceivedData {
  chatId: string;
  b2bClientId: number;
  chatRoomId: string;
  groupId: number;
  channelId: number;
  phoneNumber: string;
  senderPhoneNumber: string;
  content: string;
  subject?: string;
  imageList: string[];
  externalMessageId?: string;
  receivedAt: string; // ISO 8601
}

// SEND_RESULT
interface B2bSendResultData {
  chatId?: string;
  id?: string;
  b2bClientId: number;
  chatRoomId?: string;
  groupId?: number;
  channelId?: number;
  content: string;
  subject?: string;
  imageList: string[];
  externalMessageId?: string;
  sendPhoneNumber?: string;
  recvPhoneNumber?: string;
  fromPhoneNumber?: string;
  toPhoneNumber?: string;
  sendStatus: 'SUCCESS' | 'FAILED';
  chatType: 'SMS' | 'LMS' | 'MMS' | 'RCS' | 'KAO';
  resultStatus: string;
  resultMessage?: string;
  createdAt: string; // ISO 8601
}

// CALL_RECEIVED
interface B2bCallReceivedData {
  callHistoryId: string;
  b2bClientId: number;
  groupId: number;
  groupName: string;
  callerNumber: string;
  callerName: string;
  calledNumber: string;
  handOffReason?: string;
  receivedAt: string; // ISO 8601
}

MESSAGE_RECEIVED

고객으로부터 메시지를 수신했을 때 전송됩니다.

json
{
  "eventType": "MESSAGE_RECEIVED",
  "timestamp": "2026-04-17T14:30:00.000Z",
  "data": {
    "chatId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "b2bClientId": 5,
    "chatRoomId": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
    "groupId": 10,
    "channelId": 2,
    "phoneNumber": "07012345678",
    "senderPhoneNumber": "01098765432",
    "content": "안녕하세요, 예약 문의드립니다.",
    "imageList": [],
    "receivedAt": "2026-04-17T14:30:00.000Z"
  }
}

필드 설명

필드타입설명
eventTypeMESSAGE_RECEIVED웹훅 이벤트 타입
timestampstring웹훅 전송 시각 (ISO 8601)
data.chatIdstring메시지 ID (32자)
data.b2bClientIdnumberB2B 클라이언트 ID
data.chatRoomIdstring채팅방 ID (30자)
data.groupIdnumber그룹 ID
data.channelIdnumber채널 ID
data.phoneNumberstring수신 번호 (ZeroCall 번호)
data.senderPhoneNumberstring발신자 번호 (고객)
data.contentstring메시지 내용
data.subjectstring제목. 없으면 필드가 생략될 수 있음
data.imageListstring[]MMS 이미지 URL 배열. 수신 웹훅에서는 S3 key가 아니라 Presigned URL로 변환되어 전달됩니다.
data.externalMessageIdstring외부 메시지 ID. 없으면 필드가 생략될 수 있음
data.receivedAtstring메시지 수신 시각 (ISO 8601)

CALL_RECEIVED

고객으로부터 수신 전화가 유입되었을 때 전송됩니다. 앱 수신 푸시와 별개로, B2B 시스템이 인입 전화를 감지할 수 있도록 제공되는 이벤트입니다.

이 이벤트는 전화 연결 완료가 아니라 `인입 시점` 기준입니다. `handOffReason`은 인입 직후에는 없을 수 있으며, 값이 없으면 필드가 생략될 수 있습니다.

json
{
  "eventType": "CALL_RECEIVED",
  "timestamp": "2026-05-27T09:00:00.000Z",
  "data": {
    "callHistoryId": "call_history_xxxxxxxxxxxxxxxx",
    "b2bClientId": 5,
    "groupId": 10,
    "groupName": "테스트 그룹",
    "callerNumber": "01098765432",
    "callerName": "홍길동",
    "calledNumber": "07012345678",
    "receivedAt": "2026-05-27T09:00:00.000Z"
  }
}

필드 설명

필드타입설명
eventTypeCALL_RECEIVED웹훅 이벤트 타입
timestampstring웹훅 전송 시각 (ISO 8601)
data.callHistoryIdstring통화 이력 ID
data.b2bClientIdnumberB2B 클라이언트 ID
data.groupIdnumber수신 그룹 ID
data.groupNamestring수신 그룹 이름
data.callerNumberstring발신자 번호 (고객)
data.callerNamestring발신자 표시 이름. 연락처 매칭 결과가 없으면 발신 번호 또는 기본값으로 내려올 수 있음
data.calledNumberstring수신 번호 (ZeroCall 번호)
data.handOffReasonstring상담 연결 사유. 인입 직후에는 없을 수 있으며 없으면 필드가 생략될 수 있음
data.receivedAtstring전화 인입 시각 (ISO 8601)

SEND_RESULT

메시지 사업자가 최종 발송 결과를 회신했을 때 전송됩니다.

채널 메시지(`/chat`) 결과는 chatId/chatRoomId/sendPhoneNumber/recvPhoneNumber 형태, 인증 휴대폰 메시지(`/verified-phone-chat`) 결과는 id/fromPhoneNumber/toPhoneNumber 형태로 전달됩니다.

현재 구현상 SEND_RESULT 웹훅은 Bizppurio 결과 수신 후에만 발행되므로, payload의sendStatus는 보통SUCCESS이며 실제 전달 성공/실패는 resultStatus로 판단해야 합니다.

json
{
  "eventType": "SEND_RESULT",
  "timestamp": "2026-04-17T14:35:00.000Z",
  "data": {
    "chatId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "b2bClientId": 5,
    "chatRoomId": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
    "groupId": 10,
    "channelId": 2,
    "content": "예약이 확정되었습니다.",
    "subject": "예약 확정 안내",
    "imageList": [],
    "externalMessageId": "ext_msg_001",
    "sendPhoneNumber": "07012345678",
    "recvPhoneNumber": "01098765432",
    "sendStatus": "SUCCESS",
    "chatType": "LMS",
    "resultStatus": "4100",
    "resultMessage": "전달",
    "createdAt": "2026-04-17T14:35:00.000Z"
  }
}

필드 설명

필드타입설명
eventTypeSEND_RESULT웹훅 이벤트 타입
timestampstring웹훅 전송 시각 (ISO 8601)
data.chatIdstring메시지 ID (32자)
data.idstring인증 휴대폰 메시지 ID. `/verified-phone-chat` 결과에서 사용될 수 있음
data.b2bClientIdnumberB2B 클라이언트 ID
data.chatRoomIdstring채팅방 ID (30자). `/verified-phone-chat` 결과에는 없음
data.groupIdnumber그룹 ID. `/verified-phone-chat` 결과에는 없음
data.channelIdnumber채널 ID. `/verified-phone-chat` 결과에는 없음
data.contentstring메시지 본문
data.subjectstring제목. 없으면 필드가 생략될 수 있음
data.imageListstring[]첨부 목록
data.externalMessageIdstring요청 시 전달한 외부 메시지 ID. 없으면 생략될 수 있음
data.sendPhoneNumberstring발신 번호. `/verified-phone-chat` 결과에서는 `fromPhoneNumber`
data.recvPhoneNumberstring수신 번호. `/verified-phone-chat` 결과에서는 `toPhoneNumber`
data.sendStatusSUCCESS | FAILEDZeroCall이 메시지 사업자에게 요청을 넘긴 상태
data.chatTypeSMS | LMS | MMS | RCS | KAO메시지 타입
data.resultStatusstring최종 발송 결과 코드
data.resultMessagestring결과 코드의 한글 메시지. 없으면 필드가 생략될 수 있음
data.createdAtstring메시지 생성 시각 (ISO 8601)

sendStatus 값 (ChatSendStatus)

값설명
SUCCESSZeroCall이 메시지 사업자에게 발송 요청을 넘김
FAILED메시지 사업자 요청 단계에서 실패. 이 경우 SEND_RESULT 웹훅이 오지 않을 수 있음

chatType 값 (ChatType)

값설명
SMS단문 메시지
LMS장문 메시지
MMS멀티미디어 메시지
RCSRCS 메시지
KAO카카오 알림톡

웹훅 처리 권장사항

  • Timeout: 10초
  • HTTP 2xx 응답 시 성공 처리
  • 비동기 처리 후 즉시 응답 권장

에러 처리

API 요청 실패 시 아래 형식으로 에러가 반환됩니다.

json
{
  "status": 401,
  "code": "BB003",
  "message": "유효하지 않은 API 키입니다."
}

HTTP 상태 코드

코드설명
400잘못된 요청 (파라미터 오류)
401인증 실패 (API Key 없음/무효/만료)
402결제 필요 (크레딧 부족)
403권한 없음 (리소스 접근 불가)
404리소스 없음
409충돌 (중복 등)
429Rate Limit 초과
500서버 오류

B2B API 에러 코드 (BB-series)

코드HTTP설명
BB001404지원되지 않는 고객사
BB002401API 키 필요
BB003401유효하지 않은 API 키
BB004401만료된 API 키
BB005403그룹 접근 권한 없음
BB006403사용자 접근 권한 없음
BB007403채널 접근 권한 없음
BB008403자체 본인인증 미허용
BB009402크레딧 부족 (서비스 정지)
BB010401B2B 클라이언트 인증 실패
BB012400웹훅 중복
BB013404웹훅 미존재

권한 에러 코드 (AU-series)

코드HTTP설명
AU007401권한이 없습니다. (그룹 멤버/그룹장 권한 필요)

externalUserId, groupId 해석 실패는 AU 계열이 아닌 BB006, BB005로 반환됩니다.

사용자 에러 코드 (UR-series)

코드HTTP설명
UR001404존재하지 않는 사용자
UR002406이미 존재하는 사용자
UR005403본인인증 필요
UR006409이미 본인인증 완료
UR008409B2B 클라이언트에 이미 등록된 유저

그룹 에러 코드 (GR-series)

코드HTTP설명
GR001404존재하지 않는 그룹
GR003400그룹장은 탈퇴 불가
GR004403현재 요금제에서 AI 기능 불가
GR005404존재하지 않는 멤버

채널 에러 코드 (CH-series)

코드HTTP설명
CH001404존재하지 않는 채널
CH002406채널이 이미 존재
CH003406이미 사용중이거나 존재하지 않는 전화번호
CH004409전화번호가 다른 요청에 의해 처리 중
CH005404대표 채널 미설정

연락처 에러 코드 (CT-series)

코드HTTP설명
CT001404존재하지 않는 연락처
CT002400커스텀 속성 정의 오류
CT003400커스텀 데이터 오류
CT004403현재 요금제에서 CRM 불가
CT008409이미 존재하는 연락처

API별 주요 예외 (서비스 로직 기준)

아래 표는 실제 서비스 로직에서 throw 되는 예외 기준입니다. 공통으로 API Key/권한 검증에서 BB002~BB007, AU007 이 발생할 수 있습니다.

API주요 예외 코드비고
POST /auth/identity-verification-urlUR001`, `UR006대상 사용자 없음, 이미 본인인증 완료
/userUR001`, `UR003`, `UR007`, `UR008`, `BB008사용자 조회/삭제, 중복 등록, 자체 본인인증 비허용
/groupGR001`, `GR003`, `GR004`, `GR007`, `GR008~GR012`, `UR005그룹 멤버/요금제/착신전환/삭제 결제 제약
/channelCH001`, `CH003`, `CH004`, `CH006`, `UR001`, `GL004채널 없음/번호 점유/결제수단 미등록/타입 제약
/call-historyCH001`, `CH003`, `AU007통화기록 없음, 이의제기 불가, 권한 없음
/chat-roomCR001`, `CH001채팅방/채널 없음
/chatCR001채팅방 없음(조회/발송)
/verified-phone-chatUR001`, `UR005사용자 없음, 본인인증 미완료
/contactCT001~CT008`, `GR001연락처/CRM 유효성, 커스텀 속성, CRM 플랜 제약
/businessBS001~BS022`, `GR001`, `UR001사업자 조회/등록/연결/파일업로드 제약
/manualMA001매뉴얼 없음
/pass-time-settingPT001설정 없음

커서 기반 API(/chat-room, /call-history, /contact)는 cursor 포맷이 잘못되면 코드형 에러 외에 HTTP 400 Bad RequestException이 반환됩니다.

메시지 발송 결과 코드

`resultStatus`는 결과 코드이며, `resultMessage`는 해당 코드의 한글 메시지입니다. 성공 판정은 아래 성공 코드 기준으로 처리하세요.

성공 판정 규칙

항목값
successCodes4100, 6600, 7000
successMessage전달

SMS 결과 코드

resultStatusresultMessage
4100전달
4400음영 지역
4401단말기 전원 꺼짐
4402단말기 메시지 저장 초과
4403메시지 삭제 됨
4404가입자 위치 정보 없음
4405단말기 BUSY
4410잘못된 번호
4411NPDB 에러
4412착신거절
4413SMSC 형식오류
4414비가입자, 결번, 서비스정지
4415식별코드 오류
4420기타에러
4421타임아웃
4422단말기 일시정지
4423단말기 착신거부
4424URL SMS 미지원폰
4425단말기 호 처리 중
4426재시도 한도 초과
4427기타 단말기 문제
4428시스템 에러
4430스팸
4431발송 제한 수신거부(스팸)
4432회신번호 차단(개인)
4433회신번호 차단(기업)
4434회신번호 사전 등록제에 의한 미등록 차단
4435KISA 신고 스팸 회신 번호 차단
4436회신번호 사전 등록제 번호규칙 위반
4437중복 발신 제한
4438화이트리스트 문구 체크
4443KISA 스팸 차단

LMS/MMS 결과 코드

resultStatusresultMessage
6600전달
6601타임 아웃
6602핸드폰 호 처리 중
6603음영 지역
6604전원이 꺼져 있음
6605메시지 저장 개수 초과
6606잘못된 번호
6607서비스 일시 정지
6608기타 단말기 문제
6609착신 거절
6610기타 에러
6611통신사의 SMC 형식 오류
6612게이트웨이의 형식 오류
6613서비스 불가 단말기
6614핸드폰 호 불가 상태
6615SMC 운영자에 의해 삭제
6616통신사의 메시지 큐 초과
6617통신사의 스팸 처리
6618공정위의 스팸 처리
6619게이트웨이의 스팸 처리
6620발송 건수 초과
6621메시지의 길이 초과
6622잘못된 번호 형식
6623잘못된 데이터 형식
6624MMS 정보를 찾을 수 없음
6625NPDB 에러
6626080 수신거부(SPAM)
6627발송 제한 수신거부(SPAM)
6628회신 번호 차단(개인)
6629회신 번호 차단(기업)
6630서비스 불가 번호
6631회신 번호 사전 등록제에 의한 미등록 차단
6632KISA 신고 스팸 회신 번호 차단
6633회신 번호 사전 등록제 번호 규칙 위반
6670첨부파일 사이즈 초과(60K)

카카오(알림톡/친구톡) 결과 코드

resultStatusresultMessage
7000전달
7101카카오 형식 오류
7103Sender key (발신프로필키) 유효하지 않음
7105Sender key (발신프로필키) 존재하지 않음
7106삭제된 Sender key (발신프로필키)
7107차단 상태 Sender key (발신프로필키)
7108차단 상태 카카오톡 채널 (카카오톡 채널 운영툴에서 확인)
7109닫힌 상태 카카오톡 채널 (카카오톡 채널 운영툴에서 확인)
7110삭제된 카카오톡 채널 (카카오톡 채널 운영툴에서 확인)
7111삭제 대기 상태의 카카오톡 채널 (카카오톡 채널 운영툴에서 확인)
7112유효하지 않은 사업자번호
7125메시지 차단 상태의 카카오톡 채널 (카카오톡 채널 운영툴에서 확인)
7203친구톡 전송 시 친구 대상 아님
7204템플릿 불일치
7300기타 에러
7305성공 불확실(30일 이내 수신 가능)
7306카카오 시스템 오류
7308전화번호 오류
7311메시지가 존재하지 않음
7314메시지 길이 초과
7315템플릿 없음
7318메시지를 전송할 수 없음
7322메시지 발송 불가 시간
7323메시지 그룹 정보를 찾을 수 없음
7324재전송 메시지 존재하지 않음
7325변수 글자수 제한 초과
7326상담/봇 전환 버튼 글자수 제한 초과
7327버튼/바로 연결 내용과 템플릿 불일치
7328메시지 강조 표기 타이틀과 템플릿 불일치
7329메시지 강조 표기 타이틀 길이 제한 초과 (50 자)
7330메시지 타입과 템플릿 강조유형이 일치하지 않음
7331헤더가 템플릿과 일치하지 않음
7332헤더 길이 제한 초과(16 자)
7333아이템 하이라이트가 템플릿과 일치하지 않음
7334아이템 하이라이트 타이틀 길이 제한 초과 (이미지 없는 경우 30 자, 이미지 있는 경우 21 자)
7335아이템 하이라이트 디스크립션 길이 제한 초과 (이미지 없는 경우 19 자, 이미지 있는 경우 13 자)
7336아이템 리스트가 템플릿과 일치하지 않음
7337아이템 리스트의 아이템의 디스크립션 길이 제한 초과(23 자)
7338아이템 요약정보가 템플릿과 일치하지 않음
7339아이템 요약정보의 디스크립션 길이 제한 초과(14 자)
7340아이템 요약정보의 디스크립션에 허용되지 않은 문자 포함 (통화기호/코드, 숫자, 콤마, 소수점, 공백을 제외한 문자 포함)
7341와이드 아이템 리스트 갯수, 최소 최대 갯수 불일치
7342대표링크가 템플릿과 일치하지 않음
7351캐러셀 아이템 리스트 갯수, 최소 최대 갯수 불일치
7352캐러셀 아이템 메시지 길이 OVER
7421타임아웃
7521중복발신제한

코드 예시

cURL

bash
# 사용자 생성
curl -X POST "https://b2b-gateway.zerocall.kr/api/v1/user" \
  -H "x-api-key: b2b_xxxx_your-secret-key" \
  -H "Content-Type: application/json" \
  -d '{
    "externalUserId": "user_12345",
    "phoneNumber": "01012345678"
  }'

# 그룹 목록 조회
curl -X GET "https://b2b-gateway.zerocall.kr/api/v1/group?externalUserId=user_12345" \
  -H "x-api-key: b2b_xxxx_your-secret-key"

# 메시지 발송
curl -X POST "https://b2b-gateway.zerocall.kr/api/v1/chat?externalUserId=user_12345" \
  -H "x-api-key: b2b_xxxx_your-secret-key" \
  -H "Content-Type: application/json" \
  -d '{
    "chatRoomId": "room_123",
    "content": "안녕하세요. 예약이 확정되었습니다."
  }'

# 통화 기록 조회
curl -X GET "https://b2b-gateway.zerocall.kr/api/v1/call-history?externalUserId=user_12345&groupId=1&size=20" \
  -H "x-api-key: b2b_xxxx_your-secret-key"

JavaScript

javascript
const axios = require('axios');

const client = axios.create({
  baseURL: 'https://b2b-gateway.zerocall.kr/api/v1',
  headers: {
    'x-api-key': 'b2b_xxxx_your-secret-key',
    'Content-Type': 'application/json'
  }
});

// 사용자 생성
async function createUser(externalUserId, phoneNumber) {
  const response = await client.post('/user', {
    externalUserId,
    phoneNumber
  });
  return response.data;
}

// 그룹 목록 조회
async function getGroups(externalUserId) {
  const response = await client.get('/group', {
    params: { externalUserId }
  });
  return response.data;
}

// 메시지 발송
async function sendMessage(externalUserId, chatRoomId, content) {
  const response = await client.post('/chat', {
    chatRoomId,
    content
  }, {
    params: { externalUserId }
  });
  return response.data;
}

// 통화 기록 조회
async function getCallHistories(externalUserId, groupId, cursor) {
  const response = await client.get('/call-history', {
    params: { externalUserId, groupId, cursor, size: 20 }
  });
  return response.data;
}

Python

python
import requests

BASE_URL = 'https://b2b-gateway.zerocall.kr/api/v1'
API_KEY = 'b2b_xxxx_your-secret-key'

headers = {
    'x-api-key': API_KEY,
    'Content-Type': 'application/json'
}

# 사용자 생성
def create_user(external_user_id: str, phone_number: str) -> dict:
    response = requests.post(
        f'{BASE_URL}/user',
        headers=headers,
        json={
            'externalUserId': external_user_id,
            'phoneNumber': phone_number
        }
    )
    return response.json()

# 그룹 목록 조회
def get_groups(external_user_id: str) -> list:
    response = requests.get(
        f'{BASE_URL}/group',
        headers=headers,
        params={'externalUserId': external_user_id}
    )
    return response.json()

# 메시지 발송
def send_message(external_user_id: str, chat_room_id: str, content: str) -> dict:
    response = requests.post(
        f'{BASE_URL}/chat',
        headers=headers,
        params={'externalUserId': external_user_id},
        json={
            'chatRoomId': chat_room_id,
            'content': content
        }
    )
    return response.json()

# 통화 기록 조회
def get_call_histories(external_user_id: str, group_id: int, cursor: str = None) -> dict:
    params = {
        'externalUserId': external_user_id,
        'groupId': group_id,
        'size': 20
    }
    if cursor:
        params['cursor'] = cursor

    response = requests.get(
        f'{BASE_URL}/call-history',
        headers=headers,
        params=params
    )
    return response.json()

웹훅 수신 (Node.js)

javascript
const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook', (req, res) => {
  const { eventType, timestamp, data } = req.body;

  switch (eventType) {
    case 'MESSAGE_RECEIVED':
      console.log('메시지 수신:', data.content);
      console.log('발신자:', data.senderPhoneNumber);
      handleIncomingMessage(data);
      break;

    case 'SEND_RESULT':
      console.log('사업자 요청 상태:', data.sendStatus);
      console.log('최종 결과 코드:', data.resultStatus);
      if (!['4100', '6600', '7000'].includes(data.resultStatus)) {
        console.error('최종 발송 실패:', data.resultMessage);
      }
      handleSendResult(data);
      break;

    case 'CALL_RECEIVED':
      console.log('수신 전화 인입:', data.calledNumber);
      console.log('발신자:', data.callerNumber, data.callerName);
      handleIncomingCall(data);
      break;

  }

  // 즉시 200 응답 (비동기 처리 권장)
  res.status(200).send('OK');
});

app.listen(3000);