[PRD] Supporting Embeddable Create Ticket Form (Web)
HEADER BLOCK
| Field | Value |
|---|---|
| PM | Zhelia Alifa |
| PRD Version | 2.0 |
| Status | DRAFT |
| PRD Type | SUPPORT |
| Epic | TBD — add once Epic is created |
| Squad | CDP Squad |
| RFC Link | DRAFT RFC — Embed Ticket in Omnichannel, Phase 1 Backend · DRAFT RFC FE — Embed Ticket in Omnichannel, Phase 1 Frontend (both CRM-owned) |
| Figma Master | TBD — CDP entry point + embed panel |
| Anchor | Yes — Create Ticket & Auto-Associate from CDP (Cross-Platform) — ANCHOR (web track). Lead-squad anchor: Embed Ticket in Omnichannel. |
| Labels | epic:qontak-cdp | module:customers | feature:embed-create-ticket |
| Last Updated | 2026-06-25 |
This is a SUPPORT PRD. The embeddable Create-Ticket component is owned and built by the CRM squad.
2. SUPPORT Context
| Field | Detail |
|---|---|
| Anchor PRD | Embed Ticket in Omnichannel — ANCHOR. Its "Flexible Embeddable Component (Able to be reused for CDP)" story is the hook for this PRD — but it is a "nice to have", not Phase 1 committed scope. |
| Lead-squad Phase PRD / RFCs | CRM/Omnichannel Phase 1 — BE RFC + FE RFC. Both RFCs explicitly mark "CDP embed reuse" OUT of Phase 1 scope (architecture-friendly, not implemented). |
| This squad's contribution (CDP) | Frontend only: embed the CRM-owned ticket component inside CDP → Customer Detail → Tickets section; send the customer context via EMBED_INIT; handle the TICKET_CREATED / EMBED_CLOSE / TICKET_CREATE_ERROR messages; refresh the associated-tickets list; permission-gate the entry point. CDP does not build the ticket form, the pipeline/layout logic, or the create API — those are CRM's. |
| Integration point (contract) | The CRM Nuxt-layer embed at /embed/ticket/create loaded in an iframe, communicating via the FE RFC's versioned postMessage contract (EMBED_INIT ⇄ TICKET_CREATED/EMBED_CLOSE/TICKET_CREATE_ERROR/EMBED_RESIZE), origin-validated. See §8. |
| Handoff contract + date | CRM delivers: a CDP-reusable embed (config-driven, accepts an EMBED_INIT from a non-chat host) + a CDP embed_source value in the BE allow-list. CDP delivers: the customer-detail integration. Date: TBD — gated on CRM committing CDP reuse into a phase (currently out of Phase 1 — OQ-1, blocking). |
| Graceful degradation | If the CRM embed is unavailable / not yet reusable for CDP, the CDP Tickets section degrades to the existing "Associate existing ticket" flow only (POST /v1/leads/{contact_id}/tickets/{ticket_id}) — no create. The feature is additive and never blocks the existing associate/view behavior. |
3. One-liner + Problem
One-liner: Let a CDP user create a ticket from the Customer Detail page via the CRM's embeddable ticket form, auto-associated to the customer — at parity with the Embeddable Create Deal Form.
Problem: Today CDP only lets a user associate an existing ticket to a customer (AssociatedTickets.vue → POST /v1/leads/{contact_id}/tickets/{ticket_id}). To raise a new ticket, the user must leave the customer context for the Ticket Dashboard / Omnichannel. The equivalent Deal flow is already embeddable in CDP, so the experience is asymmetric — and support agents working a customer in CDP lose time and context switching out to create a ticket.
4. Target Users + Persona Context
| Persona | Role | Goal | Pain | Workaround |
|---|---|---|---|---|
| Primary — Support / CS Agent | Agent working a customer in CDP | Raise a ticket on the customer without leaving the customer record | CDP can't create tickets — only associate existing ones | Opens the Ticket Dashboard / Omnichannel in another tab, creates the ticket, then comes back to associate it |
| Secondary — Sales / CRM Admin | Agent/admin managing a customer's records | Keep ticket + deal creation in one place (Deal already embeds; Ticket doesn't) | Inconsistent: Deal embeds in CDP, Ticket doesn't | Treats tickets as a separate, out-of-context task |
5. Non-Goals
- Not building the ticket form — the create form, pipeline selection, and custom-layout rendering are owned by the CRM embed (FE RFC). CDP only embeds it.
- Not building the create API — ticket creation is the CRM's
POST /api/mobile/v2.8/tickets(BE RFC). CDP does not add a create endpoint. - No mobile — web only (
qontak-customer-fe); the CRM embed Phase 1 is web-only. (Mobile is the sibling SUPPORT PRD.) - No ticket viewing/editing in CDP — Phase 2/3 of the anchor (Unified Viewing & Editing) is out of scope here.
- No ticket history surface in CDP — that is the separate anchor scope.
- No change to the existing "Associate existing ticket" flow — it stays exactly as today.
- No chat-room linking from CDP — CDP has no chat room context;
room_idlinking (Deal Scenario 1) does not apply (see §8 / OQ-5).
Scope Changes
Engineering surfaces this PRD touches (controlled vocab: Backend · Frontend · Mobile · Infra · Data · Design · Docs · None). CDP-side only; CRM-owned work is a dependency, not CDP scope.
- Frontend —
qontak-customer-fe: add a Create-Ticket entry point + embed (iframe to the CRM/embed/ticket/createlayer) inAssociatedTickets.vue; sendEMBED_INITwith the customer context (qontakCustomerId, contact name/phone/email); handleEMBED_READY/TICKET_CREATED/TICKET_CREATE_ERROR/EMBED_CLOSE/EMBED_RESIZE(origin-validated); on success refresh the associated-tickets list + toast; permission-gate the entry point. - Design — Figma for the CDP Create-Ticket entry point + the embed panel placement and its loading / error / success states.
- Backend (conditional —
contact-service) — only if CRM does not auto-associate the CDP-created ticket: CDP calls its existingPOST /v1/leads/{contact_id}/tickets/{ticket_id}to associate afterTICKET_CREATED(see OQ-6). Otherwise None. - Dependency (not CDP scope) — CRM Backend + Frontend — add a CDP
embed_sourcevalue (e.g.embed-web-cdp) to the BE allow-list; make the embed component reusable for a non-chat (CDP) host; resolve Qontak One auth (see §13, OQ-1/OQ-2).
6. Constraints
| Constraint | Value |
|---|---|
| Platform | Web only (qontak-customer-fe). The CRM embed Phase 1 is web-only. |
| Feature flag | CRM gates the embed behind unify_ticket_omnichannel_view (per-team, default OFF) (BE RFC). CDP's entry point should additionally be flag-gated for staged rollout — flag name TBD with CRM (OQ-7). |
| Embed delivery | iframe loading the CRM Nuxt-layer route /embed/ticket/create (FE RFC), served from a CRM subdomain (e.g. crm.qontak.com). Sandboxed; EMBED_RESIZE-driven height. |
| Auth (grounded — open) | The CRM embed authenticates JWT Bearer (v2.8 session) called directly by the webview — not a token query param, not via IAG (BE RFC). Qontak One may authenticate via Mekari unified SSO, not v2.8 JWT — if so the v2.8 endpoints would 401. This is an unresolved, blocking dependency (BE RFC §6; FE RFC Concern #1 / OQ-1). |
| postMessage contract (grounded) | Versioned (version: 1), typed JSON messages with origin validation against an env allowlist (never '*') (FE RFC). Not a {msg: "..."} string. See §8 for the exact event/payload contract. |
data_source labeling (grounded) | Set server-side by CRM when embed: true, derived from embed_source against the allow-list embed-web-chat / embed-flutter-crm / embed-flutter-chat (client value ignored/overwritten). A CDP value (e.g. embed-web-cdp) must be added to the BE allow-list or the server coerces it to embed-web-chat. The label field is data_source, not source. (BE RFC.) |
| Contact context transport | The customer context is passed via the EMBED_INIT postMessage payload (qontakCustomerId for Qontak One; contactAccountUniqId otherwise) — not a customer_id/token URL param (FE RFC). |
| Plan scope | All Qontak One clients that have CDP + CRM Tickets. |
| Backward compat | The existing "Associate existing ticket" flow (POST /v1/leads/{contact_id}/tickets/{ticket_id}) is unchanged. |
7. New Features
7.1 Create-Ticket entry point + embed in Customer Detail (AssociatedTickets.vue)
| Element | Before | After |
|---|---|---|
Tickets section (AssociatedTickets.vue, Customer Detail menu id 4) | Only "Associate existing ticket" (POST /v1/leads/{contact_id}/tickets/{ticket_id}); no create | Add a "Create Ticket" entry that opens the CRM embed (iframe → /embed/ticket/create) in a panel, mirroring how AssociatedDeals.vue embeds the Deal form |
| Customer context | Not passed anywhere | On embed load, CDP sends EMBED_INIT with the customer context (qontakCustomerId, name/phone/email) so the CRM form auto-fills the Contact |
| On success | n/a | On TICKET_CREATED, CDP closes the embed, refreshes the associated-tickets list, and shows a success toast (server-side association — OQ-6) |
Component tree (CDP side):
AssociatedTickets.vue(existing — extend)CreateTicketEntry— "Create Ticket" button/menu item (permission-gated)TicketEmbedPanel(new) — sandboxed<iframe src="${CRM_EMBED_URL}/embed/ticket/create">postMessagelistener — origin-validatedEMBED_READY/TICKET_CREATED/TICKET_CREATE_ERROR/EMBED_CLOSE/EMBED_RESIZEEMBED_INITsender — posts the customer context onEMBED_READY
- existing
AssociatedExistingTickets/TicketsCard/FilterTickets(unchanged)
4 UI States (embed panel):
- Loading: spinner/skeleton while the iframe loads + until
EMBED_READY. - Empty: N/A (the panel always renders the CRM form once ready).
- Error: iframe fails to load or
TICKET_CREATE_ERROR→ inline error + retry / close; the agent can still use "Associate existing". - Success:
TICKET_CREATED→ panel closes, tickets list refreshes, success toast.
8. API & Webhook Behavior
CDP owns no create API. The "API behavior" here is the embed
postMessagecontract (CRM-owned, FE RFC) plus the optional CDP association call. CDP must implement to this exact contract.
| # | Behavior | Entity Affected | Triggered By | Expected Behavior | Failure Behavior |
|---|---|---|---|---|---|
| 1 | Initialise the embed with customer context | CRM embed form | CDP receives EMBED_READY from the iframe | CDP posts EMBED_INIT { version:1, roomId: null, contactName, contactPhone, contactEmail, contactAccountUniqId, qontakCustomerId, channelType, locale } to the iframe origin; the CRM form auto-fills the Contact via qontakCustomerId (Qontak One) | Origin mismatch → message dropped; if EMBED_READY never arrives within a timeout → show the embed error state |
| 2 | Create the ticket | CRM Ticket (CRM side) | Agent submits the form inside the embed | CRM calls POST /api/mobile/v2.8/tickets with embed: true, embed_source (a CDP value — OQ-3), JWT Bearer auth; sets data_source server-side; auto-associates the contact (OQ-6) | TICKET_CREATE_ERROR { errorCode, errorMessage } → CDP shows the error; ticket stays creatable via "Associate existing" |
| 3 | Notify CDP of success | — | CRM emits TICKET_CREATED | Payload { ticketId, ticketName, ticketSlug, pipelineId, pipelineName, stageId, stageName, priorityId, createdAt, dueDate, channelIntegrationRoomId }; CDP validates origin + version, then closes the embed, refreshes the tickets list, shows a success toast | If the payload is malformed / origin invalid → ignored; agent can re-open |
| 4 | Associate the created ticket to the customer | CDP ticket association | After TICKET_CREATED | Preferred: CRM auto-associates server-side via the customer context (anchor US#6). Fallback (OQ-6): CDP calls POST /v1/leads/{contact_id}/tickets/{ticket_id} with ticketId from the payload | Association fails → log cdp_ticket_embed_associate_failed; ticket still exists in CRM; surface a retry |
| 5 | Close / cancel the embed | — | Agent cancels, or after success | CRM emits EMBED_CLOSE { reason: 'user_cancel' | 'ticket_created' | 'error' } (or CDP sends EMBED_CLOSE_REQUEST); CDP tears down the panel | — |
| 6 | Resize the embed | — | Form height changes | CRM emits EMBED_RESIZE { height }; CDP sets the iframe height | — |
System Flow — CDP Create-Ticket Embed
sequenceDiagram
participant Agent
participant CDP as customer-fe (AssociatedTickets)
participant Embed as CRM embed (/embed/ticket/create)
participant CRM as CRM API (v2.8/tickets)
Agent->>CDP: Click "Create Ticket"
CDP->>Embed: load iframe
Embed-->>CDP: EMBED_READY (v1)
CDP->>Embed: EMBED_INIT { qontakCustomerId, contact..., roomId:null }
Note over Embed: single pipeline -> bypass; multiple -> pipeline list; render layout
Agent->>Embed: fill form + Submit
Embed->>CRM: POST /api/mobile/v2.8/tickets { embed:true, embed_source }
alt success
CRM-->>Embed: 201 ticket
Embed-->>CDP: TICKET_CREATED { ticketId, ... }
CDP->>CDP: validate origin+version -> close panel, refresh list, toast
opt CRM did not auto-associate (OQ-6)
CDP->>CRM: POST /v1/leads/{contact_id}/tickets/{ticketId}
end
Embed-->>CDP: EMBED_CLOSE { reason: ticket_created }
else failure
CRM-->>Embed: 4xx/5xx
Embed-->>CDP: TICKET_CREATE_ERROR { errorCode, errorMessage }
CDP->>CDP: show error; associate-existing still available
end
9. System Flow + User Stories + ACs
9.1 System Flow
- Agent opens a customer in CDP → Customer Detail → Tickets section; if they have ticket-create permission, a "Create Ticket" entry shows.
- Clicking it opens the CRM embed (
/embed/ticket/create) in a sandboxed iframe panel. - The embed posts
EMBED_READY; CDP replies withEMBED_INITcarrying the customer context (qontakCustomerId, contact details). - The CRM form auto-fills the Contact; if the account has one pipeline it renders the form directly, if multiple it shows pipeline selection first, then the layout's fields.
- Agent fills + submits; CRM creates the ticket (
POST /api/mobile/v2.8/tickets,embed:true, CDPembed_source), setsdata_sourceserver-side, and auto-associates the contact. - CRM emits
TICKET_CREATED; CDP validates origin +version, closes the panel, refreshes the associated-tickets list, shows a success toast (+ optional CDP association call — OQ-6). - Failure branches: iframe load fails → error state, associate-existing still available;
TICKET_CREATE_ERROR→ inline error; origin/version invalid → message ignored; agent cancels →EMBED_CLOSE.
9.2 User Stories
| User Story | Importance | Mockup | Technical Notes | Acceptance Criteria |
|---|---|---|---|---|
| [TCKT-S01] — Open the Create-Ticket embed from CDP As a Support/CS Agent, I want to open a Create-Ticket form from the Customer Detail page, so that I can raise a ticket without leaving the customer record. | Must Have | Figma: TBD — CDP entry point + embed panel | Before-After: Before — AssociatedTickets.vue only offers "Associate existing". After — a "Create Ticket" entry opens the CRM embed (iframe → /embed/ticket/create) in a panel, mirroring AssociatedDeals.vue. | — Happy Path — • AC-1: Given the user has ticket-create permission and the flag is ON, when they open the Tickets section, then a "Create Ticket" entry is shown. • AC-2: Given they click "Create Ticket", when the panel opens, then a sandboxed iframe loads /embed/ticket/create and a loading state shows until EMBED_READY.— Error / Unhappy Path — • ERR-1: Given the iframe fails to load, then an inline error + retry shows and "Associate existing" remains available. — Permission Model — • CAN: users with the ticket-create permission (key — OQ-4). • CANNOT: users without it — the entry is not rendered. — UI States — • Loading: spinner until EMBED_READY. • Empty: N/A. • Error: iframe-load failure message. • Success: form rendered. |
| [TCKT-S02] — Auto-fill the customer in the embed As a Support/CS Agent, I want the ticket's Contact pre-filled with the customer I'm viewing, so that I don't re-enter it. | Must Have | Figma: TBD — embed Contact field (read from CRM form) | Before-After: Before — no context passed. After — on EMBED_READY, CDP posts EMBED_INIT { qontakCustomerId, contactName, contactPhone, contactEmail, contactAccountUniqId, roomId:null, channelType, locale, version:1 }; the CRM form auto-fills the Contact via qontakCustomerId. Contact context is sent via postMessage, not a URL param (per FE RFC). | — Happy Path — • AC-1: Given the embed posts EMBED_READY, when CDP responds, then it sends EMBED_INIT to the iframe's exact origin with qontakCustomerId = the open customer.• AC-2: Given a Qontak One customer, when the form initialises, then the Contact field is pre-filled from qontakCustomerId (editable by the agent).— Error / Unhappy Path — • ERR-1: Given EMBED_INIT targets a non-allowlisted origin, then CDP does not post it (origin guard).• ERR-2: Given the customer cannot be resolved by the CRM, then the Contact field is left empty/editable (CRM behavior); CDP still allows submit. — Permission Model — • CAN: same as TCKT-S01. |
[TCKT-S03] — Confirm creation + refresh on TICKET_CREATEDAs a Support/CS Agent, I want CDP to confirm the ticket was created and show it on the customer, so that I see the result in context. | Must Have | Figma: TBD — success toast + refreshed Tickets list | Before-After: Before — n/a. After — CDP listens for the typed TICKET_CREATED message { ticketId, ticketName, pipelineId, stageId, priorityId, createdAt, dueDate, … }, validates event.origin + version:1, then closes the panel, refreshes the associated-tickets list, and toasts. (Corrected vs the earlier {msg} assumption — the FE RFC defines a typed payload.) | — Happy Path — • AC-1: Given the CRM emits TICKET_CREATED from the allowlisted origin with version:1, when CDP receives it, then it closes the embed, refreshes the tickets list (TicketStore.fetchTicketsAssociated), and shows a success toast.• AC-2: Given EMBED_CLOSE { reason }, when received, then CDP tears down the panel.• AC-3: Given EMBED_RESIZE { height }, when received, then CDP sets the iframe height.— Error / Unhappy Path — • ERR-1: Given a TICKET_CREATED from a non-allowlisted origin or wrong version, then CDP ignores it.• ERR-2: Given TICKET_CREATE_ERROR { errorCode, errorMessage }, then CDP surfaces the error inline; the panel stays open for retry.— UI States — • Loading: N/A. • Empty: N/A. • Error: TICKET_CREATE_ERROR message. • Success: toast + refreshed list. |
| [TCKT-S04] — Created ticket is associated to the customer As a Support/CS Agent, I want the created ticket linked to this customer automatically, so that it shows under their Tickets. | Must Have | Figma: N/A — data linkage | Before-After: Before — n/a. After — preferred: CRM auto-associates server-side via the customer context (anchor US#6). Fallback (OQ-6): CDP calls POST /v1/leads/{contact_id}/tickets/{ticketId} using ticketId from TICKET_CREATED. | — Happy Path — • AC-1: Given CRM auto-associates the contact server-side, when TICKET_CREATED arrives, then the refreshed list already shows the new ticket — CDP makes no association call.• AC-2: Given CRM does not auto-associate for the CDP path (OQ-6), when TICKET_CREATED arrives, then CDP calls POST /v1/leads/{contact_id}/tickets/{ticketId} and then refreshes.— Error / Unhappy Path — • ERR-1: Given the fallback association call fails, then cdp_ticket_embed_associate_failed is logged and a retry is offered; the ticket still exists in CRM. |
[TCKT-S05] — data_source labels the ticket as CDP-originatedAs a Data Analyst, I want CDP-created tickets tagged with a CDP origin, so that we can distinguish them. | Should Have | Figma: N/A — backend label | Before-After: Before — only embed-web-chat / embed-flutter-crm / embed-flutter-chat exist in the BE allow-list. After — a CDP embed_source (e.g. embed-web-cdp) is added to the BE allow-list and sent on create; CRM sets data_source server-side. (Corrected vs the earlier source: cdp-embed assumption — field is data_source, value must be allow-listed.) | — Happy Path — • AC-1: Given the embed is opened from CDP, when the ticket is created, then embed: true + the CDP embed_source are sent and the stored data_source reflects the CDP origin.— Error / Unhappy Path — • ERR-1: Given the CDP embed_source is not in the BE allow-list, then the server coerces data_source to embed-web-chat (the bug this story prevents) — blocked until the allow-list is updated (dependency / OQ-3). |
| [TCKT-S06] — Pipeline + custom layout inherited from CRM As a Support/CS Agent, I want pipeline selection + the right custom layout in the embed, so that I capture the required fields. | Should Have | Figma: see FE RFC (TicketEmbedPipelineSelect, TicketsLayoutSelector) | Before-After: Before — n/a. After — inherited from the CRM embed (FE RFC reuses useTicketPipeline / useTicketLayout): single pipeline → bypass; multiple → selection list; then dynamic fields per layout. No CDP logic. | — Happy Path — • AC-1: Given one ticket pipeline, when the embed opens, then it bypasses selection and renders the default pipeline's layout. • AC-2: Given multiple pipelines, when the embed opens, then a pipeline list shows, then the chosen pipeline's layout renders. (CRM-owned; CDP only hosts the iframe.) |
| [TCKT-S07-NEG] — No create without permission / flag (Guard Rail — from Non-Goals) As the system, when a user without ticket-create permission or with the flag OFF tries to create, the action is blocked. | Guard Rail | — | — | • NEG-1: Given the user lacks ticket-create permission, then the "Create Ticket" entry is not rendered. • NEG-2: Given the embed flag is OFF for the company, then the entry is hidden and the Tickets section behaves exactly as today (associate-existing only). |
Dependencies: TCKT-S02–S06 depend on the CRM embed being reusable for CDP (a non-chat host) — currently OUT of Phase 1 in both RFCs (OQ-1, blocking). TCKT-S02 depends on the EMBED_INIT contract; TCKT-S05 depends on the BE allow-list change (OQ-3).
Test Coverage Matrix — [TCKT-S01]
| Dimension | Coverage | Notes |
|---|---|---|
| Boundary values | ⚠️ partial | AC covers permission + flag; ⚠️ QA: slow iframe load, EMBED_READY timeout |
| State transitions | ✅ defined | open → loading → ready; load-fail → error |
| Data validation | ✅ defined | origin guard on all messages |
| Concurrency | ⚠️ TBD | ⚠️ QA: open the embed while an associate-existing modal is open |
| Network/timeout | ✅ defined | ERR-1 iframe load failure |
Test Coverage Matrix — [TCKT-S03]
| Dimension | Coverage | Notes |
|---|---|---|
| Boundary values | ⚠️ partial | ⚠️ QA: TICKET_CREATED with missing optional fields (dueDate null) |
| State transitions | ✅ defined | created → close → refresh; error → stay open |
| Data validation | ✅ defined | origin + version validation (ERR-1) |
| Concurrency | ⚠️ TBD | ⚠️ QA: duplicate TICKET_CREATED messages |
| Network/timeout | ✅ defined | ERR-2 TICKET_CREATE_ERROR |
10. Rollout
| Field | Detail |
|---|---|
| Flag | CRM unify_ticket_omnichannel_view (per-team, default OFF) + a CDP entry-point flag (name TBD — OQ-7). |
| Stage 1 — Internal QA | Enable on a QA company once CRM ships a CDP-reusable embed; verify EMBED_INIT auto-fill, TICKET_CREATED refresh, data_source, association. |
| Stage 2 — Closed Beta | 3–5 Qontak One companies; monitor create success + association rate. |
| Stage 3 — GA | Progressive per-company enable. |
| Backward compat | Associate-existing flow unchanged; create is additive and flag-gated. |
| Dependency gate | Cannot start before CRM commits CDP reuse into a phase (OQ-1) and adds the CDP embed_source (OQ-3) + resolves Qontak One auth (OQ-2). |
11. Observability
| Event Name | Trigger | Properties |
|---|---|---|
cdp_ticket_embed_opened | The Create-Ticket embed is opened from CDP | company_sso_id, contact_id, qontak_customer_id, user_id |
cdp_ticket_embed_created | TICKET_CREATED received + validated | contact_id, ticket_id, pipeline_id, data_source |
cdp_ticket_embed_create_error | TICKET_CREATE_ERROR received | contact_id, error_code |
cdp_ticket_embed_associate_failed | Fallback association call failed (OQ-6) | contact_id, ticket_id, reason |
cdp_ticket_embed_init_dropped | EMBED_INIT blocked by origin guard | company_sso_id, attempted_origin |
Dashboard owner: CDP Squad. Alerts: cdp_ticket_embed_create_error rate > 10% → Slack #cdp-ops; associate-failed > 5% → investigate. Cadence: weekly for the first 4 weeks post-GA.
12. Success Metrics
| Metric | Definition | Baseline | Target |
|---|---|---|---|
| ⭐ Adoption — CDP ticket creation | % of tickets on Qontak One customers created via the CDP embed (where flag ON) within 30 days of GA | 0 (capability is net-new) | ≥ X% (set with CRM at beta) |
| ⭐ Create reliability | created / (created + create_error) | N/A | ≥ 99% |
| Association success | associated / created | N/A | ≥ 99% |
| Parity | Ticket create available wherever Deal create is | Deal-only today | 100% of CDP customer-detail surfaces |
13. Dependencies
| Dependency | Owning Team | Deliverable Needed | Blocking? |
|---|---|---|---|
| CDP-reusable embed component | CRM/Omnichannel | Make the /embed/ticket/create layer usable by a non-chat (CDP) host (accept an EMBED_INIT without a chat room). Currently OUT of Phase 1 in both RFCs. | YES |
| Qontak One auth resolution | CRM + Platform | Confirm the embed authenticates correctly for Qontak One (v2.8 JWT vs Mekari SSO) — else the create call 401s. (BE RFC §6, FE RFC Concern #1) | YES |
CDP embed_source in BE allow-list | CRM Backend | Add a CDP value (e.g. embed-web-cdp) to ALLOWED_EMBED_SOURCES so CDP-origin tickets are labeled correctly | YES (for TCKT-S05) |
postMessage contract | CRM Frontend | The versioned EMBED_INIT/TICKET_CREATED/… contract (FE RFC) | YES |
| Ticket-create permission key | CRM + CDP | The permission key gating the CDP "Create Ticket" entry (CanCan-inherited; key TBD — OQ-4) | YES |
qontak-customer-fe integration | CDP Squad | Embed + EMBED_INIT sender + message handlers + list refresh + permission gate in AssociatedTickets.vue | YES (CDP scope) |
Dependency Graph — CDP Create-Ticket Embed
graph LR
F[CDP Create-Ticket embed] -->|BLOCKING - OUT of Phase 1| REUSE[CRM: CDP-reusable embed]
F -->|BLOCKING| AUTH[Qontak One auth v2.8 vs SSO]
F -->|BLOCKING| SRC[CRM: embed-web-cdp in allow-list]
F -->|BLOCKING| PM[postMessage contract EMBED_INIT/TICKET_CREATED]
F -->|YES| PERM[ticket-create permission key]
F -->|CDP scope| FE[customer-fe: AssociatedTickets embed]
14. Key Decisions + Alternatives Rejected
14a — Decisions Made
| ID | Decision | Rationale (grounded) |
|---|---|---|
| D-1 | Consume the CRM-owned embed; build nothing CDP-native. | Parity with the Deal embed; the form/pipeline/layout/create API are all CRM's (BE+FE RFCs). |
| D-2 | Use the FE RFC's typed postMessage contract (EMBED_INIT ⇄ TICKET_CREATED/TICKET_CREATE_ERROR/EMBED_CLOSE/EMBED_RESIZE, version:1, origin-validated). | Grounded in the FE RFC. Supersedes the earlier {msg:"Ticket successfully created"} assumption (that mirrored the shipped Deal embed, not the new contract). |
| D-3 | Pass the customer context via EMBED_INIT postMessage, keyed on qontakCustomerId — not a customer_id/token URL param. | Grounded in the FE RFC (EMBED_INIT payload) + Deal parity (qontak_customer_id). |
| D-4 | Label via data_source from an allow-listed embed_source; add a CDP value to the BE allow-list. | Grounded in the BE RFC (server-side data_source; client value ignored). Replaces the earlier source=cdp / source: cdp-embed assumption. |
| D-5 | Degrade gracefully to associate-existing if the embed is unavailable. | The embed is additive; CDP already has the associate path. |
14b — Alternatives Rejected
| Alternative | Why Rejected |
|---|---|
{msg: "..."} string postMessage (mirror the shipped Deal embed) | The new FE RFC defines a typed, versioned payload — the {msg} shape would not interoperate with the Phase 1 component. |
token + customer_id in the iframe URL | The BE RFC uses direct JWT Bearer (no token query param); the FE RFC passes contact via EMBED_INIT, not the URL. |
source: cdp-embed label | Field is data_source; value must be in the BE allow-list — an arbitrary client value is overwritten server-side. |
| Build a native CDP ticket form | Duplicates CRM logic and perpetuates the parity-lag the anchor exists to remove. |
15. Open Questions
| # | Type | Question | Mitigation / Default | Owner | Deadline |
|---|---|---|---|---|---|
| OQ-1 | Risk (blocking) | CDP embed reuse is OUT of Phase 1 in both RFCs. When will CRM commit a CDP-reusable embed (non-chat host) to a phase? Without it, this PRD cannot be built. | Escalate to CRM/Omnichannel to schedule CDP reuse; until then CDP stays associate-existing only. | PM + CRM | 2026-07-04 |
| OQ-2 | Risk (blocking) | Qontak One auth: does the embed work with Qontak One's auth (v2.8 JWT vs Mekari SSO), or will the create call 401? | Confirm with CRM + Platform before beta; block GA until resolved. | CRM + Platform | 2026-07-04 |
| OQ-3 | Decision | What CDP embed_source value (e.g. embed-web-cdp) and who adds it to the BE ALLOWED_EMBED_SOURCES allow-list? | Propose embed-web-cdp; CRM Backend adds it. | CDP + CRM BE | 2026-07-04 |
| OQ-4 | Open Question | Which permission key gates the CDP "Create Ticket" entry? (CanCan-inherited per anchor US#5, but the CDP key is unnamed.) | Confirm with CRM; reuse the ticket-create permission. | PM + CDP | 2026-07-04 |
| OQ-5 | Open Question | Does a CDP-created ticket need any chat/room linkage (Deal Scenario 1 auto-links a chat)? CDP has no room. | Default: no room linkage for CDP (roomId: null in EMBED_INIT). | PM | 2026-07-04 |
| OQ-6 | Decision | Does CRM auto-associate the contact for the CDP path, or must CDP call POST /v1/leads/{contact_id}/tickets/{ticketId} after TICKET_CREATED? | Confirm with CRM; if not auto-associated, CDP makes the call (adds a Backend touch — see Scope Changes). | CDP + CRM | 2026-07-04 |
| OQ-7 | Open Question | CDP entry-point feature-flag name + relation to CRM's unify_ticket_omnichannel_view. | Add a CDP-side flag for staged rollout; align with CRM. | CDP | 2026-07-04 |
| OQ-8 | Open Question | Embed surface: inline panel inside the Tickets section vs a side sheet/modal (Deal uses a panel)? | Default: inline panel like the Deal embed; confirm with Figma. | Design | 2026-07-04 |
PRD CHANGELOG
| Version | Date | By | Section | Type | Summary |
|---|---|---|---|---|---|
| 2.0 | 2026-06-25 | Reformat + RFC grounding | All | REWRITE | Reformatted from the Product-Planning template into the AI-SDLC template (HEADER BLOCK, ToC, §1–15, ## Scope Changes section, split Mockup / Technical Notes story columns). Grounded against the CRM BE RFC + FE RFC and corrected the embed contract: typed EMBED_INIT/TICKET_CREATED postMessage (not {msg}); data_source via allow-listed embed_source (not source=cdp); contact via qontakCustomerId in EMBED_INIT (not a customer_id/token URL param); JWT/SSO auth open. Surfaced the headline risk: CDP reuse is OUT of Phase 1 in both RFCs (OQ-1, blocking). Stories rewritten as TCKT-S01–S07-NEG with Gherkin ACs + 2 test matrices. |
| 1.0 | 2026-06-22 | Initial draft | All | CREATED | First draft (Product-Planning format) mirroring the Embeddable Create Deal Form. |