Saltar al contenido principal

Platform Leads Troubleshooting Runbook

Use this runbook when a landing-page demo/contact submission is missing from Chatwoot, the FIXX Leads screen is empty or forbidden, or the Chatwoot deep-link is missing on a lead that exists in the database.

Architecture and API shapes: Lead Module. Who may open the menu: Platform Billing — Access Control.


Scope​

LayerCodepaths
Domainapps/backend/src/lead/domain/lead.entity.ts, lead-search.util.ts, chatwoot-conversation-url.util.ts
Applicationcreate-lead.service.ts, lead-query.service.ts
Infrastructurelead.repository.ts, chatwoot-lead-destination.adapter.ts, sendgrid-lead-notification.adapter.ts
Interfaceslead.controller.ts (POST /leads); platform-leads.controller.ts (GET /platform-billing/leads*)
PWAPlatformBillingLeadsPage.tsx, PlatformBillingLeadDetailPage.tsx, platformBillingService.ts

1. Symptom triage​

SymptomLikely layer
Landing form returns 400CreateLeadDto validation (source / locale enums, required fields)
Landing form returns 201 but no rowWrong API host, or looking at a different database than the landing page
Row exists, Chatwoot conversation does notAdapter skipped (wrong/missing deploy names) or Chatwoot HTTP failed
Chatwoot conversation exists, inbox has no “Open in Chatwoot”setChatwootRefs failed, or CHATWOOT_BASE_URL / CHATWOOT_ACCOUNT_ID missing on the API that serves the inbox
FIXX Leads menu missingRemote Config pwaMenu not published, or current business is not FIXX
List/detail 403 FlowPOS staff access only.PlatformAdminGuard — no active FIXX super/owner business_user
List empty with filterssearch / source / locale; % and _ are literal, not wildcards
Inquiry text missing on the tableExpected — inquiryMessage and notes are detail-only

2. Capture path (POST /leads)​

Expected sequence:

  1. Landing page POST /leads (no auth).
  2. LeadRepository.insert writes the row (businessId is always null from the controller).
  3. Chatwoot dispatch and SendGrid notify run in parallel.
  4. On Chatwoot success, setChatwootRefs stores conversation/contact ids.
  5. Response is { "id": "<uuid>" } regardless of Chatwoot/email outcome.

Checks:

  • Confirm source is demo or contact and locale is es or en.
  • A 201 with an id means persist succeeded. Do not treat a missing Chatwoot ticket as a failed HTTP call.
  • Search the lead table by email/createdAt, not by Chatwoot.

3. Chatwoot dispatch skipped or failed​

The adapter returns null (skip) when any of these is unset: CHATWOOT_BASE_URL, CHATWOOT_API_TOKEN, CHATWOOT_ACCOUNT_ID, and an inbox id.

Inbox id is CHATWOOT_MARKETING_INBOX_ID or, if that is empty, CHATWOOT_INBOX_ID. Production currently uses the single-inbox fallback.

Do not configure CHATWOOT_API_URL. That name is not read. An earlier version of this adapter did, while deploy set CHATWOOT_BASE_URL — every lead persisted and none reached Chatwoot. Logs: Chatwoot env vars not configured — skipping lead dispatch.

When env is present but HTTP fails:

  1. Contact search/create uses the prospect email.
  2. Conversation create uses inbox_id: Number(inboxId) and additional_attributes.lead_id.
  3. Label marketing-lead and the private activity note are best-effort; a label failure still returns conversation/contact ids if the conversation was created.
  4. Errors log at ERROR and still leave the 201 intact.

A conversation in Chatwoot without stored ids on lead means dispatch worked on Chatwoot’s side but setChatwootRefs failed (Lead <id> saved but Chatwoot ids were not persisted). The inbox cannot reconstruct the URL without the conversation id.


4. Operator inbox (GET /platform-billing/leads)​

Expected UI flow:

  1. Select FIXX in the business switcher (is_platform_operator).
  2. Open Platform Billing → Leads (/platform-billing/leads).
  3. Search commits on submit; source/locale apply immediately.
  4. Row click → /platform-billing/leads/:leadId.

Auth failures:

  • 403 from the API → platformBilling.forbidden in the PWA. Confirm an active FIXX business_user with unique_role_name in { super, owner }. JWT “current business” is not used.
  • Menu missing after a pwaMenu.json edit → publish Firebase Remote Config. Temporary icon fallback: menu-icon.ts path /platform-billing/leads.

Search surprises:

  • Pattern is %term% after escaping %, _, and \. Typing % does not mean “match anything”.
  • search longer than 100 characters is rejected by the query DTO (400).
  • size max is 100; default 20. Sort is createdAt descending only.

Chatwoot link on detail is null when:

  • chatwootConversationId is null (skip, failed dispatch, or pre-ref historical row), or
  • CHATWOOT_BASE_URL / CHATWOOT_ACCOUNT_ID are missing on the API process serving the inbox (they are read again in LeadQueryService.getLead, not copied from the row).

URL shape (derived, not stored):

{CHATWOOT_BASE_URL}/app/accounts/{CHATWOOT_ACCOUNT_ID}/conversations/{chatwootConversationId}

5. Email did not arrive​

SendGrid is independent of Chatwoot. Production/beta send to LEAD_NOTIFICATION_EMAIL. Staging/localhost skip unless COMMUNICATION_EMAIL_OVERRIDE is a single valid address.

A missing email with a 201 is not a capture failure. Check DEPLOY_ENV and whether the SendGrid adapter logged a missing variable or a SendGrid error (lead.id only in that log).