Bỏ qua để đến nội dung

Kênh webhook & bot (adapter pattern)

  • 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
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
  1. Ingress (webhook.controller.ts) tra connection theo :token, chọn adapter theo channel_type, giải mã webhook_secret, gọi adapter.verify (sai → 401).
  2. Adapter.parse map payload thô → NormalizedInboundMessage[] (1 update có thể ra nhiều message).
  3. 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.
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.

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

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_url body {thread_id, conversation_id, message_id, text} + header X-Signature (HMAC bằng webhook_secret).

Gửi xong stamp external_message_id (emitEvents:false) để chặn re-entry — giống loop-guard của SMTP.

  1. Tạo adapters/zalo.ts implement WebhookAdapter (verify theo mac, parse payload OA → NormalizedInboundMessage).
  2. Thêm 1 dòng vào adapters/index.ts: zalo: zaloAdapter.
  3. (outbound) thêm nhánh zalo trong channel-sender.ts.
  4. Frontend: thêm channel descriptor + đăng ký ở plugins/channels.ts.

Pipeline, ingress, schema không đổi.