Skip to main content

RFC Summary: Phase 1 — Bot & AI Creation Parity: Backend (qontak.com)

Full RFC: Phase 1 — Bot & AI Creation Parity: Backend (qontak.com)
Status: IDEA | Type: backend / enhancement
Author: Ardian Pradipta | Target: 2026-Q3
PRD: crm/consistent-deal-ticket-creation/prds/phase-1-bot-ai-creation-parity.md


Problem

Deals and tickets created by Chatbot or Agentic AI show no creator attribution — data_source is always 'open-api' and the timeline displays "<deleted user>" instead of "Bot" or "AI". There is no way for CRM users to distinguish bot/AI-created records from regular API creates, and the timeline lacks the chat-context note that manual creates get.


What's Being Built

  • creator_flag param — Chatbot/Agentic AI callers send creator_flag: 'bot' or 'agentic_ai' on POST create; data_source records the channel origin.
  • Timeline creator labelaudits.username stores the creator_flag value; audit.rb#mapping_who renders "Bot" or "Agentic AI" in the timeline.
  • Timeline chat note — When channel_integration_room_id is also sent, a note is created: Crm::HubChannelTicket for deals, Crm::HubChannelTicketV2 for tickets.
  • Contact associationcustomer_ids on ticket create dispatches a new LinkTicketByCustomerIdWorker to associate contacts via PeopleTicket join model.

Component Architecture

flowchart TB
caller([API caller]) --> api[/v4 · v3.1 deals · tickets API/]
api --> svc[create handler]
svc -->|as_user creator_flag| audit_write[(audits table\nusername=creator_flag)]
svc -->|data_source=creator_flag| db[(Postgres)]
svc -->|room_id present - deals| hct[Crm::HubChannelTicket]
svc -->|room_id present - tickets| hctv2[Crm::HubChannelTicketV2]
hct --> audit_write
hctv2 --> audit_write
hct --> db
hctv2 --> db
svc -->|customer_ids async - tickets| wrk[LinkTicketByCustomerIdWorker]
wrk --> svc2[LinkTicketByCustomerIdService]
svc2 --> db

Repo Map (files touched)

flowchart LR
subgraph crm[BE: qontak.com]
subgraph controllers[Controllers]
v4d["api/v4/deals.rb"]
v4t["api/v4/tickets.rb"]
v31d["api/v3dot1/deals.rb"]
v31t["api/v3dot1/tickets.rb"]
end

subgraph notes[Timeline Notes]
hct["models/crm/hub_channel_ticket.rb\n(Crm::DealNote — deals)"]
hctv2["models/crm/hub_channel_ticket_v2.rb\n(TicketNote — tickets)"]
note_tmpl["services/hub/ticket/new_ticket_service.rb\n(create_note_ticket — V2 template)"]
end

subgraph contact_assoc[Contact Association]
svc_d["services/contacts/link_deal_by_customer_id_service.rb"]
wrk_d["workers/contacts/link_deal_by_customer_id_worker.rb"]
svc_t["services/contacts/link_ticket_by_customer_id_service.rb [NEW]"]
wrk_t["workers/contacts/link_ticket_by_customer_id_worker.rb [NEW]"]
pt["models/people_ticket.rb"]
end

subgraph models[Models / Schema]
deal_m["models/crm/deal.rb"]
schema["db/schema.rb"]
audit["models/audit.rb\n(mapping_who — extended)"]
end
end

v4d -->|create_notes| hct
v4t -->|create_ticket_notes NEW| hctv2
note_tmpl -.->|template for| hctv2
v4d -->|customer_ids async| wrk_d
v4t -->|customer_ids async| wrk_t
wrk_d --> svc_d
wrk_t --> svc_t
svc_d --> pt
svc_t --> pt

Sequence Diagrams

Happy path — bot deal creation with timeline note

sequenceDiagram
actor Bot as Chatbot API
participant LB as Load Balancer
participant API as CRM API pod
participant DB as Postgres primary

Bot->>LB: POST /v4/deals (creator_flag:bot, room_id)
LB->>API: HTTP
API->>API: permit params, validate creator_flag
API->>API: set audited_actor = creator_flag (bot)
note over API: Audited.as_user(bot) block begins
API->>DB: INSERT deal (data_source=bot, channel_integration_room_id)<br/>audits.username=bot
DB-->>API: commit
API->>DB: INSERT Crm::HubChannelTicket note<br/>audits.username=bot
DB-->>API: commit
note over API: Audited.as_user block ends
API-->>Bot: 201 Created

Failure path — invalid creator_flag

sequenceDiagram
actor Bot as Chatbot API
participant API as CRM API pod

Bot->>API: POST /v4/deals (creator_flag:invalid)
API->>API: validate creator_flag
alt value not bot or agentic_ai
API-->>Bot: 422 INVALID_CREATOR_FLAG
else absent
API->>API: skip mapping, default behavior
API-->>Bot: 201 (data_source unchanged)
end

Key Technical Decisions

#DecisionChosenRationale
1Creator label mechanismaudits.username via as_user(creator_flag) + mapping_who extensiondata_source is for channel-origin tracking, not creator labels. audits.username already handles system actors (hub, Qontak system). No schema change.
2Timeline note modelCrm::HubChannelTicket for deals, Crm::HubChannelTicketV2 for tickets (not one shared model)Two distinct STI subclasses with different parent associations (crm_deal vs tickets). Using the wrong type would break timeline rendering.
3Feature flagNone — param is self-gatingcreator_flag absent → existing behavior. Producers control when they start sending it.
4customer_ids for ticketsNew LinkTicketByCustomerIdService + WorkerDeal counterpart exists. Generalising it adds polymorphic complexity not worth the risk. Two ~50-line files following an established template.

APIs at a Glance

MethodEndpointStatusOwner
POST/api/v4/dealsextended — add optional creator_flag, customer_ids, async_lead_assocCRM BE
POST/api/v4/ticketsextended — same paramsCRM BE
POST/api/v3.1/dealsextended — same paramsCRM BE
POST/api/v3.1/ticketsextended — same paramsCRM BE

Request params: creator_flag (optional, "bot"|"agentic_ai", case-sensitive), customer_ids (optional, string[]), async_lead_assoc (optional, boolean, default false).

201 response: Unchanged shape — data_source is NOT exposed in the entity. creator_flag is not echoed back.


Open Blockers

#BlockerOwnerBlocks
1Chatbot/AI squads must send creator_flag + room_id in create payloadChatbot + AI squadsFeature inactive until payload delivered
2InfoSec approverCRM Tech Lead + InfoSec§7 Ready marker cannot flip to yes

Rollout

StageAudienceGo/No-go evidence
1 — Producers adoptChatbot/AI teamscreator_flag present in production API calls; zero unexpected creator_flag.invalid WARN logs over 7 days
2 — FE reads timelineCRM FE teamCreator label displays correctly in timeline

Stop conditions: Revert PR if creator_flag.invalid error rate spikes in production or timeline labels regress for existing manual creates.

Rollback: Revert the PR. No DB changes to undo — the data_source column accepts existing values natively. Any LinkTicketByCustomerIdWorker jobs in the queue become no-ops (the service guard ticket_not_found handles orphaned jobs gracefully).


Generated from RFC: Phase 1 — Bot & AI Creation Parity: Backend (qontak.com) · 2026-07-03