Content Retrieval
The inbound webhook delivers signals only (payload). When you receive a kind: "message" signal, fetch the actual conversation content with the retrieval API.
This page covers the retrieval API for fetching conversation content after receiving an inbound signal (kind: "message"). As the content model expands to attachments, rich messages, and so on, they will be added to this page.
Recent messages for a specific customer
GET /api/agent/rooms/{userKey}/messages?brand=mybrand&max=20
- Authentication:
Authorization: Bearer blumnb-...(BrandRead or higher) max: 1–300 (default 100). Returned newest first.
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": "I'd like to ask about a refund",
"timestampUnixMs": 1751850000000
}
]
}
messages[] items:
| Field | Type | Description |
|---|---|---|
seq | number | Journal sequence (corresponds to seq in the webhook signal) |
userKey | string | Customer key |
sessionId | string | Session identifier |
kind | string | message / reference / expired / agent / ended / deleted |
text | string | Message body |
timestampUnixMs | number | Time received (ms) |
serial | string | Only on outbound (agent) and deletion (deleted) items — same value as the serial in the send response and webhook signals |
deleted | boolean | true only on a deleted outbound message. Absent otherwise |
Mapping a signal to its content
When you receive a message signal, the text of the first kind: "message" item in the response above is the customer message that just arrived (results are newest first).
Consultation room list
Returns a summary of in-progress consultation rooms (per customer).
GET /api/agent/rooms?brand=mybrand&max=50
{
"ok": true,
"brand": "mybrand",
"rooms": [
{
"userKey": "Ab3d_x9Kq2Lm",
"lastSeq": 42,
"lastText": "I'd like to ask about a refund",
"lastKind": "message",
"lastTimestampUnixMs": 1751850000000,
"count": 7,
"ended": false
}
]
}
Conversation history (convId)
Consultations are grouped into conversations (convId) per session window. Use these when you need per-conversation aggregation or the full transcript rather than the per-customer recent messages above. It is the same path the CLI history command uses, and because Inquiry.id in the knowledge graph is <convId>#<turn>, you can build that key from REST alone.
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": "How much does TalkBridge cost?", "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": "How much does TalkBridge cost?", "at": "2026-09-08T01:23:10+00:00" },
{ "role": "agent", "text": "Hello! Our plans are …", "at": "2026-09-08T01:25:02+00:00" }
]
}
- Authentication: BrandRead or higher.
lines[].roleis one ofcustomer,agent,bot,system. - If there is no such conversation:
404withNO_CONV.
Images · attachments
When a customer sends a photo, video, voice message, or file with KakaoTalk's upload feature, the body (text) carries it as [type] Kakao CDN URL. The type is photo, video, audio, or file.
[photo] https://talk.kakaocdn.net/…/….jpeg
Photos sent together as a bundle arrive as one message, with one line per photo in the body (separated by a newline, \n). If the customer typed a caption with the attachment, it follows the URL on the same line, after a space.
[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" }
- Handling: Attachments are also handled as a
messageevent plus content retrieval. Split the body into lines, then read the[type]and URL from each line. If you read only the first line, you miss the second and later photos of a bundle. You can also tell the type apart by the extension (photojpg·png/ videomp4/ voicem4a·mp3/ filepdf, and so on). lines[].textin the conversation history (/api/agent/conversation) uses the same format.- Other URLs in the body are not attachments. Links a customer types into the chat arrive as the same body text. Parse the URL and confirm exactly that the host is
talk.kakaocdn.net.
Example — extract only the photos from the body (Node.js)
Runs on Node.js 18+ (built-in fetch) with no external packages. It fetches a customer's recent messages, splits each body into lines, and keeps only [photo] lines whose host is the Kakao CDN.
const API_BASE = 'https://api.talkbridge.io';
const TOKEN = process.env.BRIDGE_TOKEN; // "blumnb-..."
// One line = "[type] URL" or "[type] URL caption"
const LINE = /^\[(photo|video|audio|file)\] (\S+)(?: (.*))?$/;
/** Pull every attachment out of a message body (text) — a photo bundle spans several lines. */
function parseAttachments(text) {
const out = [];
for (const line of (text ?? '').split('\n')) {
const m = LINE.exec(line.trim());
if (!m) continue; // a plain text line
let url;
try { url = new URL(m[2]); } catch { continue; }
// Treat non-Kakao-CDN URLs as not attachments (links the customer typed · SSRF protection).
if (url.protocol !== 'https:' || url.hostname !== 'talk.kakaocdn.net') continue;
out.push({ type: m[1], url: url.href, comment: m[3] ?? '' });
}
return out;
}
/** Collect only the photo URLs from a customer's recent messages. */
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') // customer messages only
.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: '' } ]
- Photos with the same
seqwere sent together as one bundle by the customer. - To also collect videos or files, change the
a.type === 'photo'condition. - If you receive
messagesignals by webhook, applyparseAttachmentsto thetextof the item whoseseqmatches the signal.
Kakao does not guarantee how long these URLs are kept, and we have confirmed that a ten-day-old URL no longer opens. If you need attachments in your consultation records, download them right after you receive them and keep them in your own storage, and link your stored copy in the record instead of the URL.
- When downloading, follow "When you download and keep attachments" in the Inbound server security checklist — if your server downloads a URL that is not on the Kakao CDN, it can be abused to make requests into your internal network (SSRF).
- Attachments may contain customers' personal information. Manage them according to your privacy policy and retention period.
Handling summary by event
kind | Retrieval needed? | Handling |
|---|---|---|
message | ✅ | Fetch content → process text / image |
reference | ✗ | Handle as "new consultation connected" — planted values are in the signal's reference · lastReference (Connect Metadata) |
expired | ✗ | Handle as "session expired" |
ended | ✗ | Handle as "consultation ended" |
agent | ✗ | Ignore (self-send echo) |