수신 내용 조회
수신 웹훅은 신호만 전달합니다(payload). kind: "message" 신호를 받으면, 실제 대화 내용은 조회 API로 가져옵니다.
이 페이지는 수신 신호(kind: "message")를 받은 뒤 대화 내용을 가져오는 조회 API 를 다룹니다. 첨부·리치 메시지 등 내용 모델이 확장되면 이 페이지에 추가됩니다.
특정 고객의 최근 메시지
GET /api/agent/rooms/{userKey}/messages?brand=mybrand&max=20
- 인증:
Authorization: Bearer blumnb-...(BrandRead 이상) max: 1~300(기본 100). 최신순으로 반환.
curl "https://api.talkbridge.io/api/agent/rooms/Ab3d_x9Kq2Lm/messages?brand=mybrand&max=5" \
-H "Authorization: Bearer blumnb-..."
{
"ok": true,
"brand": "mybrand",
"userKey": "Ab3d_x9Kq2Lm",
"messages": [
{
"seq": 42,
"userKey": "Ab3d_x9Kq2Lm",
"sessionId": "ses_...",
"kind": "message",
"text": "환불 문의드립니다",
"timestampUnixMs": 1751850000000
}
]
}
messages[] 항목:
| 필드 | 타입 | 설명 |
|---|---|---|
seq | number | 저널 시퀀스(웹훅 신호의 seq와 대응) |
userKey | string | 고객 키 |
sessionId | string | 세션 식별자 |
kind | string | message / reference / expired / agent / ended / deleted |
text | string | 메시지 본문 |
timestampUnixMs | number | 수신 시각(ms) |
serial | string | 발신(agent)·삭제(deleted) 항목에만 — 발신 응답·웹훅 신호의 serial 과 같은 값 |
deleted | boolean | 삭제된 발신에만 true. 그 외에는 필드가 없습니다 |
신호 → 본문 매핑
message 신호를 받으면, 위 응답에서 첫 kind: "message" 항목의 text 가 방금 도착한 고객 메시지입니다(최신순 반환).
상담방 목록
진행 중인 상담방(고객별) 요약을 조회합니다.
GET /api/agent/rooms?brand=mybrand&max=50
{
"ok": true,
"brand": "mybrand",
"rooms": [
{
"userKey": "Ab3d_x9Kq2Lm",
"lastSeq": 42,
"lastText": "환불 문의드립니다",
"lastKind": "message",
"lastTimestampUnixMs": 1751850000000,
"count": 7,
"ended": false
}
]
}
대화 이력 (convId)
상담은 세션 창 단위의 대화(convId)로 묶입니다. 고객별 최근 메시지(위)가 아니라 대화 단위로 집계하거나 전체 transcript 가 필요할 때 씁니다. CLI history 와 같은 경로이며, 상담지식 그래프의 Inquiry.id 가 <convId>#<turn> 이라 REST 만으로도 그 키를 만들 수 있습니다.
GET /api/agent/conversations?brand=mybrand
[
{
"brand": "mybrand",
"conversations": [
{ "convId": "ad05d3f3…", "userKey": "Ab3d_x9Kq2Lm", "seq": 17, "sessionId": "ses_…", "ended": false,
"lastActivity": "2026-09-09T01:24:33+00:00", "last": "톡브릿지 가격이 어떻게되나요?", "count": 4 }
]
}
]
GET /api/agent/conversation?brand=mybrand&convId=ad05d3f3…
{
"convId": "ad05d3f3…", "userKey": "Ab3d_x9Kq2Lm", "seq": 17, "sessionId": "ses_…", "ended": false,
"lines": [
{ "role": "customer", "text": "톡브릿지 가격이 어떻게되나요?", "at": "2026-09-08T01:23:10+00:00" },
{ "role": "agent", "text": "안녕하세요, 요금제는 …", "at": "2026-09-08T01:25:02+00:00" }
]
}
- 인증: BrandRead 이상.
lines[].role은customer·agent·bot·system. - 대화가 없으면
404와NO_CONV.
이미지 · 첨부
고객이 카카오톡의 업로드 기능으로 사진·동영상·음성·파일을 보내면, 본문(text)에 [종류] 카카오 CDN URL 형식으로 담겨 옵니다. 종류는 photo · video · audio · file 입니다.
[photo] https://talk.kakaocdn.net/…/….jpeg
여러 장을 묶어 보낸 사진은 메시지 1건으로 오며, 본문에 사진마다 한 줄씩(줄바꿈 \n 구분) 담깁니다. 고객이 첨부와 함께 문구를 입력했다면 같은 줄의 URL 뒤에 공백을 두고 붙습니다.
[photo] https://talk.kakaocdn.net/…/i_6f66….jpeg
[photo] https://talk.kakaocdn.net/…/i_cdfc….jpeg
[photo] https://talk.kakaocdn.net/…/i_6d1c….jpeg
{ "kind": "message", "text": "[photo] https://talk.kakaocdn.net/…/i_6f66….jpeg\n[photo] https://talk.kakaocdn.net/…/i_cdfc….jpeg" }
- 처리: 첨부도
message이벤트 + 본문 조회로 처리합니다. 본문을 줄 단위로 나눈 뒤 각 줄에서[종류]와 URL 을 읽으세요. 첫 줄만 읽으면 묶음사진의 두 번째 장부터 빠집니다. 확장자로도 종류(사진jpg·png/ 동영상mp4/ 음성m4a·mp3/ 파일pdf등)를 구분할 수 있습니다. - 대화 이력(
/api/agent/conversation)의lines[].text도 같은 형식입니다. - 본문의 다른 URL 은 첨부가 아닙니다. 고객이 채팅에 직접 입력한 링크도 같은 본문 텍스트로 옵니다. 호스트가
talk.kakaocdn.net인지 URL 을 파싱해 정확히 확인하세요.
예제 — 본문에서 사진만 추출하기 (Node.js)
Node.js 18 이상(내장 fetch)에서 외부 패키지 없이 동작합니다. 고객의 최근 메시지를 조회해, 본문을 줄 단위로 나누고 [photo] 줄 중 호스트가 카카오 CDN 인 URL 만 골라냅니다.
const API_BASE = 'https://api.talkbridge.io';
const TOKEN = process.env.BRIDGE_TOKEN; // "blumnb-..."
// 한 줄 = "[종류] URL" 또는 "[종류] URL 문구"
const LINE = /^\[(photo|video|audio|file)\] (\S+)(?: (.*))?$/;
/** 메시지 본문(text)에서 첨부를 모두 꺼낸다 — 묶음사진은 여러 줄이다. */
function parseAttachments(text) {
const out = [];
for (const line of (text ?? '').split('\n')) {
const m = LINE.exec(line.trim());
if (!m) continue; // 일반 텍스트 줄
let url;
try { url = new URL(m[2]); } catch { continue; }
// 카카오 CDN 이 아닌 URL 은 첨부로 취급하지 않는다(고객이 입력한 링크 · SSRF 방지).
if (url.protocol !== 'https:' || url.hostname !== 'talk.kakaocdn.net') continue;
out.push({ type: m[1], url: url.href, comment: m[3] ?? '' });
}
return out;
}
/** 특정 고객의 최근 메시지에서 사진 URL 만 모은다. */
async function fetchPhotoUrls(brand, userKey, max = 20) {
const res = await fetch(
`${API_BASE}/api/agent/rooms/${encodeURIComponent(userKey)}/messages?brand=${brand}&max=${max}`,
{ headers: { Authorization: `Bearer ${TOKEN}` } });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { messages } = await res.json();
return messages
.filter(m => m.kind === 'message') // 고객 발화만
.flatMap(m => parseAttachments(m.text)
.filter(a => a.type === 'photo')
.map(a => ({ seq: m.seq, url: a.url, comment: a.comment })));
}
fetchPhotoUrls('mybrand', 'Ab3d_x9Kq2Lm').then(photos => console.log(photos));
// [ { seq: 43, url: 'https://talk.kakaocdn.net/…/i_6f66….jpeg', comment: '' },
// { seq: 43, url: 'https://talk.kakaocdn.net/…/i_cdfc….jpeg', comment: '' } ]
- 같은
seq의 사진들은 고객이 한 번에 묶어 보낸 것입니다. - 동영상·파일도 필요하면
a.type === 'photo'조건을 바꾸면 됩니다. - 웹훅으로
message신호를 받는 구조라면, 신호의seq와 같은 항목의text에parseAttachments를 적용하세요.
카카오는 이 URL 의 보관 기간을 보장하지 않으며, 열흘 지난 URL 이 열리지 않는 것을 확인했습니다. 상담 기록에 첨부를 남겨야 한다면 수신 직후 내려받아 여러분의 저장소에 보관하고, 기록에는 URL 대신 보관한 사본을 연결하세요.
- 내려받을 때는 수신 서버 보안 체크리스트의 「첨부를 내려받아 보관할 때」를 따르세요 — 카카오 CDN 이 아닌 URL 을 서버가 내려받으면 내부망 요청(SSRF)으로 악용될 수 있습니다.
- 첨부에는 고객의 개인정보가 담길 수 있습니다. 여러분의 개인정보 처리방침과 보관 기간에 맞춰 관리하세요.
이벤트별 처리 요약
kind | 조회 필요? | 처리 |
|---|---|---|
message | ✅ | 본문 조회 → 텍스트/이미지 처리 |
reference | ✗ | "새 상담 연결" 로 처리 — 심어 둔 값은 신호의 reference·lastReference 에(상담 연결 메타) |
expired | ✗ | "세션 만료" 로 처리 |
ended | ✗ | "상담 종료" 로 처리 |
agent | ✗ | 무시(자기 발신 echo) |