conversations
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConversations
对话
When to Use
使用场景
Use this skill when building anything that reads an inbox: a shared inbox UI, a helpdesk sync, a "show me this customer's history" tool, or an agent that needs the thread before it answers.
GET /v1/messages当你构建任何需要读取收件箱的功能时使用此技能:共享收件箱UI、客服同步工具、“查看客户历史记录”工具,或是需要先获取对话线程再回复的Agent。
GET /v1/messagesWhat a conversation is
什么是对话
One thread with one contact, spanning every channel. A contact who writes on WhatsApp and later by email stays in the same conversation, and lists what it has carried.
channels| Field | Meaning |
|---|---|
| Thread ID. Same value as |
| The key the thread is filed under. Not always a phone number — see below. |
| The contact, when resolved. Absent on group threads. |
| Every channel this thread has carried. |
| Denormalized preview ( |
| Sender that last handled the thread. Pass it back as |
| Inbound messages not yet marked read. |
| |
| Present on group chats: |
与一个联系人的单一线程,覆盖所有渠道。如果一个联系人先通过WhatsApp发送消息,之后又发邮件,这些消息会归属于同一个对话,字段会列出该对话涉及的所有渠道。
channels| 字段 | 含义 |
|---|---|
| 线程ID。与消息和webhook中的 |
| 线程的归档标识。不一定是电话号码——详见下文。 |
| 已解析的联系人信息。群组线程中无此字段。 |
| 该对话涉及的所有渠道。 |
| 非规范化的预览信息(包含 |
| 最后处理该线程的发送方。回复时需将其作为 |
| 未标记为已读的入站消息数量。 |
| 当联系人设置了WhatsApp用户名时,包含 |
| 群组聊天中存在此字段:包含 |
contactIdentifier is not a phone number
contactIdentifier 并非电话号码
It holds whichever identifier keys the thread: an E.164 phone, a WhatsApp BSUID (), a numeric chat ID (Telegram/Instagram/Messenger), or a group JID (). Parsing it as a phone number breaks on every non-SMS channel. Use and to decide how to render it.
US.13491208655302741918<id>@g.uschannelsgroup它存储的是用于标识线程的任意标识:E.164格式的电话号码、WhatsApp BSUID(如)、数字聊天ID(Telegram/Instagram/Messenger),或是群组JID(如)。若将其解析为电话号码,在非SMS渠道上会失效。请通过和字段决定如何展示该标识。
US.13491208655302741918<id>@g.uschannelsgroupEndpoints
接口端点
These are not in the generated SDKs yet. Use REST.
| Method | Path | Purpose |
|---|---|---|
| | List threads, most recently active first. |
| | One thread. |
| | Messages in the thread, newest first. |
| | Reset |
这些接口尚未包含在生成的SDK中,请使用REST方式调用。
| 请求方法 | 路径 | 用途 |
|---|---|---|
| | 列出线程,按最近活跃时间排序。 |
| | 获取单个线程。 |
| | 获取线程中的消息,按最新消息排序。 |
| | 将 |
List threads
列出线程
bash
curl "https://api.zavu.dev/v1/conversations?limit=25" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"Filters: scopes to one number, keeps only threads that have carried that channel.
senderIdchannelbash
curl "https://api.zavu.dev/v1/conversations?senderId=sender_12345&channel=whatsapp" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"TypeScript:
typescript
const res = await fetch("https://api.zavu.dev/v1/conversations?limit=25", {
headers: { Authorization: `Bearer ${process.env.ZAVUDEV_API_KEY}` },
});
const { items, nextCursor } = await res.json();bash
curl "https://api.zavu.dev/v1/conversations?limit=25" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"过滤条件:用于限定单个号码,仅保留涉及该渠道的线程。
senderIdchannelbash
curl "https://api.zavu.dev/v1/conversations?senderId=sender_12345&channel=whatsapp" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"TypeScript示例:
typescript
const res = await fetch("https://api.zavu.dev/v1/conversations?limit=25", {
headers: { Authorization: `Bearer ${process.env.ZAVUDEV_API_KEY}` },
});
const { items, nextCursor } = await res.json();Search threads
搜索线程
search+1 (555) 123-456715551234567bash
curl "https://api.zavu.dev/v1/conversations?search=%2B56912345678" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"search+1 (555) 123-456715551234567bash
curl "https://api.zavu.dev/v1/conversations?search=%2B56912345678" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"by email, by its local part, or by group name
按邮箱、邮箱本地部分或群组名称搜索
curl "https://api.zavu.dev/v1/conversations?search=maria"
-H "Authorization: Bearer $ZAVUDEV_API_KEY"
-H "Authorization: Bearer $ZAVUDEV_API_KEY"
Matching is by whole word with prefix matching on the last term: `mar` finds `maria@example.com`, and `+1555` finds `+15551234567`. A fragment from the middle or the end of a number (`4567`) does **not** match — search the full number or a prefix.
Three things to know before you build on it:
- **It does not search message bodies.** `search` matches the thread's identity, never what was said inside it.
- **Results are ranked by relevance, not recency.** The usual "most recently active first" ordering does not apply while `search` is set.
- **An empty `search` returns nothing**, not everything. Drop the parameter to list all threads.
`search` combines with `senderId` and `channel`, and paginates with `cursor` like any other list.
```typescript
const params = new URLSearchParams({ search: "+56912345678", channel: "whatsapp" });
const res = await fetch(`https://api.zavu.dev/v1/conversations?${params}`, {
headers: { Authorization: `Bearer ${process.env.ZAVUDEV_API_KEY}` },
});
const { items } = await res.json();curl "https://api.zavu.dev/v1/conversations?search=maria"
-H "Authorization: Bearer $ZAVUDEV_API_KEY"
-H "Authorization: Bearer $ZAVUDEV_API_KEY"
匹配规则为整词匹配,最后一个词支持前缀匹配:`mar`会匹配`maria@example.com`,`+1555`会匹配`+15551234567`。号码中间或末尾的片段(如`4567`)**不会**匹配——请搜索完整号码或前缀。
使用前需了解三点:
- **不搜索消息内容**。`search`仅匹配线程的标识信息,不会匹配消息内的内容。
- **结果按相关性排序,而非最新活跃时间**。设置`search`参数后,默认的“按最近活跃时间排序”规则不生效。
- **空`search`参数返回空结果,而非所有线程**。如需列出所有线程,请移除该参数。
`search`可与`senderId`和`channel`组合使用,也可通过`cursor`参数分页,与其他列表接口一致。
```typescript
const params = new URLSearchParams({ search: "+56912345678", channel: "whatsapp" });
const res = await fetch(`https://api.zavu.dev/v1/conversations?${params}`, {
headers: { Authorization: `Bearer ${process.env.ZAVUDEV_API_KEY}` },
});
const { items } = await res.json();Paginate
分页
nextCursorcursornextCursornulltypescript
async function allConversations() {
const out = [];
let cursor: string | undefined;
do {
const url = new URL("https://api.zavu.dev/v1/conversations");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.ZAVUDEV_API_KEY}` },
});
const page = await res.json();
out.push(...page.items);
cursor = page.nextCursor ?? undefined;
} while (cursor);
return out;
}The filter is applied to each page after it is fetched, so a filtered page can come back shorter than — even empty — while is still set. Keep paginating until is ; do not stop on a short page.
channellimitnextCursornextCursornullnextCursorcursornextCursornulltypescript
async function allConversations() {
const out = [];
let cursor: string | undefined;
do {
const url = new URL("https://api.zavu.dev/v1/conversations");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.ZAVUDEV_API_KEY}` },
});
const page = await res.json();
out.push(...page.items);
cursor = page.nextCursor ?? undefined;
} while (cursor);
return out;
}channellimitnextCursornextCursornullTelling the two sides apart
区分消息方向
Every message carries ( or ). Use it — cannot tell them apart, because an inbound message is stored as too. Deriving it by comparing against works for one-to-one threads and breaks on groups, where is the participant rather than the thread key.
directioninboundoutboundstatusdeliveredtocontactIdentifierfromtypescript
for (const m of items) {
const mine = m.direction === "outbound";
console.log(`${mine ? "→" : "←"} ${m.text}`);
}每条消息都带有字段(表示入站,表示出站)。请使用此字段——字段无法区分消息方向,因为入站消息的也会存储为。通过比较和来判断方向的方法仅适用于一对一线程,在群组线程中会失效,因为群组中的是参与者而非线程标识。
directioninboundoutboundstatusstatusdeliveredtocontactIdentifierfromtypescript
for (const m of items) {
const mine = m.direction === "outbound";
console.log(`${mine ? "→" : "←"} ${m.text}`);
}Read a thread
读取线程消息
bash
curl "https://api.zavu.dev/v1/conversations/$CONV_ID/messages?limit=50" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"Returns the same objects as , newest first, across every channel in the thread.
MessageGET /v1/messagesbash
curl "https://api.zavu.dev/v1/conversations/$CONV_ID/messages?limit=50" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"返回的对象与接口相同,按最新消息排序,覆盖线程涉及的所有渠道。
MessageGET /v1/messagesMark read
标记已读
bash
curl -X POST "https://api.zavu.dev/v1/conversations/$CONV_ID/read" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"This clears the counter in your inbox. It does not send a read receipt to the contact. To show a WhatsApp read receipt plus a typing indicator, use on the inbound message instead.
POST /v1/messages/{messageId}/typingbash
curl -X POST "https://api.zavu.dev/v1/conversations/$CONV_ID/read" \
-H "Authorization: Bearer $ZAVUDEV_API_KEY"此操作仅清空你的收件箱中的未读计数,不会向联系人发送已读回执。如需发送WhatsApp已读回执及输入状态,请对入站消息调用接口。
POST /v1/messages/{messageId}/typingReplying in a thread
回复线程消息
Send with , addressing the thread's identifier and passing its as so the reply leaves from the number the contact already knows.
POST /v1/messagessenderIdZavu-Sendertypescript
const conv = await getConversation(conversationId);
await zavu.messages.send({
to: conv.contactIdentifier,
channel: conv.lastMessage.channel,
text: "Thanks — shipping today.",
"Zavu-Sender": conv.senderId,
});The sender override is a header param inside the send params object, not a second argument.
Two constraints still apply when replying:
- WhatsApp 24-hour window. Outside it, free-form text is rejected with ; send an approved template instead. See the
whatsapp_window_closedskill.whatsapp-templates - Channel availability. Reply on a channel the sender actually has. Trust on
channels, not the presence of a phone number.GET /v1/senders/{senderId}
调用发送消息,指定线程的标识,并将线程的作为参数传入,确保回复使用联系人熟悉的号码发送。
POST /v1/messagessenderIdZavu-Sendertypescript
const conv = await getConversation(conversationId);
await zavu.messages.send({
to: conv.contactIdentifier,
channel: conv.lastMessage.channel,
text: "Thanks — shipping today.",
"Zavu-Sender": conv.senderId,
});发送方覆盖参数是发送参数对象中的一个头参数,而非第二个参数。
回复时仍需遵守两个限制:
- WhatsApp 24小时窗口。超出该窗口后,自由格式文本会被拒绝并返回错误;请改用已审核的模板发送。详见
whatsapp_window_closed技能。whatsapp-templates - 渠道可用性。请使用发送方实际拥有的渠道回复。请信任接口返回的
GET /v1/senders/{senderId}字段,而非仅根据电话号码判断。channels
Getting a conversation from a webhook
从Webhook获取对话
message.inbounddata.conversationIdnullRecovery order:
- Use when present.
data.conversationId - Otherwise take it from the event, which carries the id.
conversation.new - Otherwise , whose
GET /v1/messages/{messageId}is always populated.conversationId
Do not fall back to listing conversations and matching on the phone number: on a busy project the thread may not be at the top yet, and the identifier may be a BSUID rather than the number you expected.
message.inbounddata.conversationIdnull恢复顺序:
- 若存在,则使用该值。
data.conversationId - 否则从事件中获取ID。
conversation.new - 否则调用接口,该接口返回的
GET /v1/messages/{messageId}始终已填充。conversationId
请勿通过列出所有线程并匹配电话号码的方式获取:在繁忙的项目中,该线程可能尚未出现在列表顶部,且标识可能是BSUID而非你预期的电话号码。
Building an inbox
构建收件箱
The API gives you threads, messages, and read state. It does not store assignment, open/done status, internal notes, or saved replies — that workspace state belongs to your app. Key it by , which is stable across channels and survives WhatsApp identity re-keying (a BSUID-only thread that later gains a phone number keeps its id).
conversation.idFor live updates, subscribe a webhook to , , , , , and , then patch your local view. Polling is the fallback when you cannot receive webhooks. See the skill.
message.inboundmessage.sentmessage.deliveredmessage.readmessage.failedconversation.newGET /v1/conversationswebhook-setup该API提供线程、消息和已读状态,但不存储分配信息、工单状态、内部备注或快捷回复——这些工作区状态属于你的应用。请使用作为键,该ID在所有渠道中保持稳定,且在WhatsApp标识重新关联时(仅含BSUID的线程后来绑定了电话号码)仍会保留。
conversation.id如需实时更新,请订阅Webhook的、、、、和事件,然后更新本地视图。当无法接收Webhook时,可通过轮询作为备选方案。详见技能。
message.inboundmessage.sentmessage.deliveredmessage.readmessage.failedconversation.newGET /v1/conversationswebhook-setupGotchas
注意事项
| Trap | Reality |
|---|---|
Treating | It can be a BSUID, chat ID, or group JID. |
| Building a cursor | Cursors are opaque; only echo |
| Stopping pagination on a short page | The |
Expecting | Absent on group threads and unresolved contacts. |
Expecting | It only clears your own counter. |
Assuming | It is |
| 误区 | 实际情况 |
|---|---|
将 | 它可以是BSUID、聊天ID或群组JID。 |
| 手动构造cursor | Cursor是不透明的,仅需将 |
| 因页面内容少而停止分页 | |
认为 | 群组线程和未解析的联系人中无此字段。 |
认为 | 它仅清空你自己的未读计数。 |
认为 | 全新线程的该字段为 |