본문으로 건너뛰기

수신 내용 조회

수신 웹훅은 신호만 전달합니다(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[] 항목:

필드타입설명
seqnumber저널 시퀀스(웹훅 신호의 seq와 대응)
userKeystring고객 키
sessionIdstring세션 식별자
kindstringmessage / reference / expired / agent / ended / deleted
textstring메시지 본문
timestampUnixMsnumber수신 시각(ms)
serialstring발신(agent)·삭제(deleted) 항목에만 — 발신 응답·웹훅 신호의 serial 과 같은 값
deletedboolean삭제된 발신에만 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 를 적용하세요.
카카오 CDN URL 은 영구 주소가 아닙니다

카카오는 이 URL 의 보관 기간을 보장하지 않으며, 열흘 지난 URL 이 열리지 않는 것을 확인했습니다. 상담 기록에 첨부를 남겨야 한다면 수신 직후 내려받아 여러분의 저장소에 보관하고, 기록에는 URL 대신 보관한 사본을 연결하세요.

  • 내려받을 때는 수신 서버 보안 체크리스트의 「첨부를 내려받아 보관할 때」를 따르세요 — 카카오 CDN 이 아닌 URL 을 서버가 내려받으면 내부망 요청(SSRF)으로 악용될 수 있습니다.
  • 첨부에는 고객의 개인정보가 담길 수 있습니다. 여러분의 개인정보 처리방침과 보관 기간에 맞춰 관리하세요.

이벤트별 처리 요약​

kind조회 필요?처리
message✅본문 조회 → 텍스트/이미지 처리
reference✗"새 상담 연결" 로 처리 — 심어 둔 값은 신호의 reference·lastReference 에(상담 연결 메타)
expired✗"세션 만료" 로 처리
ended✗"상담 종료" 로 처리
agent✗무시(자기 발신 echo)