Skip to content

Conversation Hub ​

Conversation Hub is the reusable cross-channel session contract for website support, Telegram operators, authors, and future IM providers.

Start with the support integration guide. This page is the API overview, not a production acceptance statement.

P0/P1 Integration ​

  1. Create or upsert a tenant conversation.
  2. Issue a short-lived Widget Session from your server.
  3. Link the Widget channel and the Telegram operator/author channel.
  4. Register an event Webhook, verify HMAC, deduplicate by event_id, and persist events in your deduplicated inbox.
  5. Send replies with a stable client_message_id; use reply_to_message_id for threaded replies.

The customer server keeps API keys and Connected Account identifiers. The browser receives only the Widget Session token.

Base URL ​

text
https://staging-api.open4x.com

Production uses https://api.open4x.com only after separate activation. Verify the API, Console and Widget custom domains before starting a staging POC.

Core Endpoints ​

MethodEndpointPurpose
POST/v1/conversationsCreate or upsert a conversation by external ID
POST/v1/conversations/{id}/widget-sessionIssue a 15–60 minute Widget Session
POST/v1/conversations/{id}/channelsLink Widget or Telegram channel
POST/v1/conversations/{id}/messagesCreate a public or internal message
GET/v1/conversations/{id}/messagesRead paginated history
GET/v1/conversations/eventsRead tenant-wide incremental events
POST/v1/conversations/webhooksRegister signed event delivery
POST/v1/conversations/telegram/identities/bindCreate one-time Telegram binding link
GET/v1/conversations/credentials/statusVerify the API key policy without exposing its secret
GET/v1/conversations/inboxRead a tenant-scoped work queue
PATCH/v1/conversations/{id}Update status, priority, or locale
POST/v1/conversations/{id}/assignmentsAssign or transfer a creator/support agent
POST/v1/conversations/{id}/participantsAdd or restore a conversation participant
PATCH/v1/conversations/{id}/participants/{participantId}Update participant status or display data
DELETE/v1/conversations/{id}/participants/{participantId}Remove a participant and deactivate its channels
POST/v1/conversations/{id}/presencePublish online, typing, away, or offline state
POST/v1/conversations/{id}/messages/{messageId}/receiptsRecord delivered/read state
POST/v1/conversations/deliveries/{deliveryId}/retryRetry a dead-letter channel delivery
GET/v1/conversations/{id}/deliveriesInspect provider delivery attempts and errors
POST/v1/conversations/attachments/{attachmentId}/scan-resultPublish tenant scanner result

API keys need conversation:read, conversation:write, and conversation:events:write as appropriate. JWT requests are scoped to the authenticated tenant.

Message Contract ​

json
{
  "channel_type": "telegram_operator",
  "sender_role": "creator",
  "visibility": "public",
  "text": "您好,我来帮您处理。",
  "client_message_id": "knv-reply-1001",
  "reply_to_message_id": "msg_...",
  "source_channel_id": "chn_...",
  "audience_mode": "direct",
  "recipient_participant_ids": ["part_customer_1"]
}

internal_note must use visibility: internal. Internal messages are never delivered to Widget or Telegram. Reusing a client_message_id with the same payload returns the original message; changing the payload returns 409.

Audience routing ​

Public messages support three audience modes:

ModeMeaning
allDeliver to every active channel in the conversation. This is the backwards-compatible default.
directDeliver only to the listed recipient_participant_ids.
relayDeliver to listed recipients as a support-agent-mediated relay. It must include relay_from_message_id.

For an order conversation, the recommended policy is A (customer) → B (support) and C (creator) → B, with B explicitly choosing whether to reply to A, C, or both. A Widget Session can carry participant_id and default recipient_participant_ids, so the browser never chooses another participant.

Channels should be linked with the participant they represent:

json
{
  "channel_type": "telegram",
  "provider": "telegram",
  "external_channel_id": "123456789",
  "connected_account_id": "ca_...",
  "participant_id": "part_creator_1"
}

Low-code SDK ​

The repository includes a reusable client in sdk/:

  • open4x-conversation-client.ts is the server-side client;
  • open4x-widget.ts mounts the hosted Widget without exposing an API Key;
  • react/Open4XSupportWidget.tsx is the React wrapper;
  • open4x-web-component.ts provides <open4x-support-widget> for plain HTML, Vue, Svelte, and other frameworks.

The customer only needs a server session endpoint. It validates the logged-in user and order, calls Open4X with the Bridge Key, and returns the short-lived session_token. The browser can then use sessionUrl or the hosted loader. The SDK's createWidgetSessionForContext helper can upsert a conversation and issue the session in one server call, so an existing order/ticket database does not need an Open4X-specific schema migration. The customer still owns the authorization check and its own external ID mapping.

The supported integration levels are: hosted HTML loader for zero-build pages, the browser controller for custom UI, the framework-neutral custom element for Vue/Svelte/plain HTML, and the React component for React applications. All browser variants use the same short-lived session contract.

The same Conversation Hub model supports more than two parties. Add customer, creator, and support_agent participants, bind each Telegram or Widget channel with its participant_id, then use audience_mode: direct for a private A/B or C/B message. A support agent can use relay with relay_from_message_id when the business requires a mediated exchange.

Telegram Loop ​

Knoveria creates a Telegram binding link, the operator opens the Bot, and Open4X emits telegram.identity.started. Knoveria then links the operator's Telegram chat to the relevant conversation. A Telegram inbound message is stored once, the source channel is skipped, and the message is delivered to the Widget channel. The reverse direction follows the same rule.

Telegram users must start the Bot before a private Bot message can be delivered. Bot credentials stay in an active telegram-bot Connected Account and are never sent by the browser.

The telegram.identity.started event includes binding_id, identity_id, external_user_id, external_chat_id, telegram_user_id, participant_id, the envelope event_id, and the per-conversation sequence.

Operations and Attachments ​

Assignments are tenant-scoped and use customer-owned identifiers. replace_active_role: true ends other active assignments with the same role before adding the new assignee. Presence is a heartbeat-style record: online/away expires within 120 seconds and typing within 15 seconds. Receipt state is monotonic and cannot move from read back to delivered.

Attachment requests must include a tenant-owned storage key or public HTTPS download URL, filename, MIME type, and byte size. Open4X never downloads arbitrary URLs. New records are pending; the tenant’s scanner must set clean before a provider adapter is allowed to expose the file. The current text delivery path does not claim full binary support for every channel.

Webhook HMAC and Recovery ​

The signature covers <unix_timestamp>.<raw_body> and is sent as X-Open4X-Event-Signature: sha256=<hex>. Verify the timestamp and signature before parsing. Use event_id for deduplication and the opaque cursor only after the event is persisted in the customer inbox.

Delivery has at most eight total attempts, including the first attempt. The seven retry delays are 30, 60, 120, 240, 480, 960 and 1920 seconds, measured after each failed attempt. These are scheduling minimums; the periodic worker may process them later. Requests time out after 10 seconds; only HTTP 2xx is success. Final failure becomes dead_letter. Authorized bridges can call POST /v1/conversations/events/{eventId}/replay; the event ID is preserved. Use the dedicated webhook secret, not the Bridge API Key. The SDK's default signature timestamp tolerance is 300 seconds. See the OpenAPI contract.

Every channel delivery also reports environment and dry_run. A dry_run=true result is simulated and must not be treated as Telegram delivery confirmation.