Kênh webhook & bot (adapter pattern)
Vị trí mã nguồn
Phần tiêu đề “Vị trí mã nguồn”Thư mụcbackend/extensions/helpdesk/src/endpoints
- controllers/webhook.controller.ts ingress: lookup token → verify → parse → pipeline
Thư mụcservices/inbound/adapters/
- types.ts WebhookAdapter interface (verify + parse)
- generic.ts HMAC-SHA256 verify + JSON contract
- telegram.ts secret_token verify + Update parse
- index.ts registry (channel_type → adapter)
- services/inbound/webhook-pipeline.ts NormalizedInboundMessage + processInboundWebhook
- services/outbound/channel-sender.ts telegram sendMessage / generic POST
- services/contact-choke-point.ts ensureContactForExternalIdentity
Luồng inbound
Phần tiêu đề “Luồng inbound”Provider ──POST──► /helpdesk/webhook/:token (public) │ lookup hd_connections theo webhook_token │ adapterForChannel(channel_type) → generic | telegram │ adapter.verify(req, secret) → 401 nếu sai chữ ký │ adapter.parse(req) → NormalizedInboundMessage[] ▼processInboundWebhook(ctx, msg, conn) dedup(external_message_id) → resolveInbox(conn) → ensureContactForExternalIdentity → thread by (inbox, external_thread_id) → create/append message → attachments- Ingress (
webhook.controller.ts) tra connection theo:token, chọn adapter theochannel_type, giải mãwebhook_secret, gọiadapter.verify(sai → 401). - Adapter.parse map payload thô →
NormalizedInboundMessage[](1 update có thể ra nhiều message). - Pipeline dedup theo
external_message_id, resolve inbox (connection→1 inbox, không plus-address), find-or-create contact theo external identity, thread theo(inbox, external_thread_id), tạo mới hoặc append, lưu attachments. Resolved → reopen.
Contract chung: NormalizedInboundMessage
Phần tiêu đề “Contract chung: NormalizedInboundMessage”interface NormalizedInboundMessage { channel: string // 'telegram' | 'webhook' externalThreadId: string // chat id → khoá thread hội thoại externalMessageId?: string // dedup author: { externalId: string; name?; email?; phone?; avatarUrl? } text?: string; html?: string; subject?: string attachments?: Array<{ filename; url?; content?; contentType? }>}Khác email: thread theo external_thread_id (không phải Message-ID/subject), định danh theo platform user id (không phải email) — ensureContactForExternalIdentity dedup theo email→phone→tạo mới (gắn metadata.identities.<platform>). Threading giữ liên tục nên mỗi (inbox, thread) là một hội thoại kéo dài.
Adapter interface
Phần tiêu đề “Adapter interface”interface WebhookAdapter { channel: string verify(req: AdapterRequest, secret: string): boolean // sai → 401 parse(req: AdapterRequest): NormalizedInboundMessage[]}| Nguồn | verify | parse |
|---|---|---|
| generic | hex(HMAC_SHA256(rawBody, secret)) khớp header X-Signature (chấp nhận prefix sha256=) |
JSON {thread_id, message_id, from{id,name,email,phone}, text|html, subject, attachments[]} |
| telegram | header X-Telegram-Bot-Api-Secret-Token === secret |
Telegram Update → chat.id=thread, from=author, text/caption; media-only bị bỏ ở v1 |
Outbound (reply)
Phần tiêu đề “Outbound (reply)”deliverOutgoing(messageId) là choke point duy nhất (dùng chung cho agent reply / automation / first message). Nó channel-aware:
if (channel === 'telegram' || channel === 'webhook') { await deliverChannelReply(...); return }if (channel !== 'email') return// … SMTP- telegram:
POST api.telegram.org/bot<bot_token>/sendMessage { chat_id: external_thread_id, text }. - generic:
POST outbound_urlbody{thread_id, conversation_id, message_id, text}+ headerX-Signature(HMAC bằngwebhook_secret).
Gửi xong stamp external_message_id (emitEvents:false) để chặn re-entry — giống loop-guard của SMTP.
Thêm một nguồn mới (vd Zalo)
Phần tiêu đề “Thêm một nguồn mới (vd Zalo)”- Tạo
adapters/zalo.tsimplementWebhookAdapter(verify theomac, parse payload OA →NormalizedInboundMessage). - Thêm 1 dòng vào
adapters/index.ts:zalo: zaloAdapter. - (outbound) thêm nhánh
zalotrongchannel-sender.ts. - Frontend: thêm channel descriptor + đăng ký ở
plugins/channels.ts.
Pipeline, ingress, schema không đổi.
Related links
Phần tiêu đề “Related links”- Kết nối webhook & bot — hướng dẫn thao tác
- Inbound: IMAP → ticket — pipeline email tương ứng
- Data model —
hd_connections/hd_conversations