Skip to content

feat(dashboard,api-service): WhatsApp Embedded Signup with inbound connection test fixes NV-8062#11591

Open
scopsy wants to merge 9 commits into
nextfrom
cursor/whatsapp-embedded-signup-inbound-test
Open

feat(dashboard,api-service): WhatsApp Embedded Signup with inbound connection test fixes NV-8062#11591
scopsy wants to merge 9 commits into
nextfrom
cursor/whatsapp-embedded-signup-inbound-test

Conversation

@scopsy

@scopsy scopsy commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

What changed? Why was the change needed?

What changed

This PR adds a feature-flagged WhatsApp Embedded Signup onboarding flow to remove manual credential copy/paste. On the dashboard, users complete Meta/Facebook OAuth, then the backend exchanges the auth code for tokens, validates and registers the WhatsApp phone with Meta, persists Novu-managed credentials, and auto-configures webhooks. It also extends WhatsApp test-template sending to support templated body parameters and improves error classification/UX (including template-not-available guidance).

Affected areas

  • api: Adds POST /integrations/whatsapp/embedded-signup, new DTO/use case, Meta Graph helpers for token exchange + phone registration, and credential resolution for Novu-managed integrations; extends template sending with bodyParameters and improves Meta error handling.
  • dashboard: Implements the Meta-branded “Log in with Facebook” button, embedded inbound confirmation UI (save subscriber phone + wa.me deep link), and refactors WhatsApp setup guide panels/hooks/components.
  • shared: Extends credential interface and feature flag enum for embedded signup enablement.
  • dal: Adds isNovuManaged to the integration credentials schema.

Key technical decisions

  • Novu-managed mode stores isNovuManaged: true and derives WhatsApp App ID/secret from platform environment variables for webhook/chat adapter configuration.
  • Embedded signup persists credentials with from set to the business display phone, and updates WhatsApp test-template selection to sample_order_confirmation with required body parameters.

Testing

  • Updates/extends unit coverage for the WhatsApp test-template use case (including template selection and bodyParameters for Novu-managed credentials). Manual verification is required for the full OAuth + webhook listening connection flow.

Adds WhatsApp Embedded Signup (Tech Provider flow) for agent onboarding, replacing the manual credential copy-paste process with a one-click Facebook login.

Backend (apps/api)

  • POST /integrations/whatsapp/embedded-signup — exchanges Meta auth code for an access token, persists credentials as isNovuManaged: true, registers the phone number with Meta, and auto-configures the webhook
  • resolveWhatsAppAppSecret / resolveWhatsAppAppId helpers in whatsapp-credentials.utils.ts — resolve platform env vars for Novu-managed integrations so the webhook configuration and chat adapter work without the user supplying an App Secret
  • sendWhatsAppTemplate extended with bodyParameters support for future use
  • Error code 131058 classified in classifyMetaError (test number restriction)
  • Feature-flagged behind IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED

Dashboard (apps/dashboard)

  • WhatsAppEmbeddedSignupButton — brand-accurate Meta Facebook login button (#1877F2) that drives the FB SDK OAuth popup and posts the result to the signup endpoint
  • EmbeddedSignupInboundTestPanel — two-phase panel: (1) save subscriber phone, (2) show business number + "Open in WhatsApp" deep link; connection confirmed when first inbound webhook arrives via ListeningStatus
  • ConnectAndTestPanel extracted to its own file; dead skipConnectStep / usesMetaSampleTemplate props and TestStatus.pending state removed
  • useConnectSubscriberPhone hook — shared subscriber phone save logic (validate → patch → invalidate)
  • ReadOnlyValueRow promoted to setup-guide-primitives
  • whatsapp-setup-guide.tsx split from 875 lines into focused modules

Connection flow

User clicks "Log in with Facebook"
  → FB SDK OAuth popup → WA_EMBEDDED_SIGNUP postMessage
  → POST /integrations/whatsapp/embedded-signup
      • token exchange, phone registration, webhook subscription
      • credentials.from = display_phone_number
  → EmbeddedSignupInboundTestPanel Phase 1: save personal phone to connect: subscriber
  → EmbeddedSignupInboundTestPanel Phase 2: wa.me deep link to business number
  → User sends message → ListeningStatus confirms connectedAt

Manual credentials path is unchanged.

Related: https://linear.app/novu/issue/NV-8062

Screenshots

Expand for optional sections

Related enterprise PR

Special notes for your reviewer

  • The embedded signup path is off by default (IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED + NOVU_WHATSAPP_APP_ID / NOVU_WHATSAPP_APP_SECRET / NOVU_WHATSAPP_CONFIG_ID must all be set). Manual credentials path is completely unaffected.
  • credentials.from is used to store the business display phone number for the inbound panel deep link. This reuses an existing credential slot that is otherwise unused for WhatsApp integrations.

Made with Cursor

Greptile Summary

This PR adds a feature-flagged WhatsApp Embedded Signup flow (Meta Tech Provider path) for agent onboarding, replacing the manual credential copy-paste with a Facebook OAuth popup. The backend exchanges the auth code for tokens, validates the phone/WABA ownership against Meta, persists isNovuManaged: true credentials, registers the phone number, and auto-configures the webhook; the dashboard adds the branded Facebook login button and a two-phase inbound connection test panel.

  • Backend: New POST /integrations/whatsapp/embedded-signup use case with full agent-integration linkage validation and Meta ownership checks; resolveWhatsAppAppSecret/resolveWhatsAppAppId helpers allow Novu-managed integrations to skip per-user App Secret storage; sendWhatsAppTemplate extended with bodyParameters and the test-template use case now selects sample_order_confirmation + body params for Novu-managed credentials.
  • Dashboard: WhatsAppEmbeddedSignupButton drives the FB SDK OAuth popup with a fixed isTrustedFacebookOrigin check, EmbeddedSignupInboundTestPanel handles the two-phase "save phone → wa.me deep link" UX; the existing whatsapp-setup-guide.tsx is refactored from 875 lines into focused modules.
  • Shared/DAL: isNovuManaged field added to the credential interface and integration schema; IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED feature flag added.

Confidence Score: 5/5

Safe to merge. All previously flagged concerns (agent-integration linkage validation, Meta ownership verification via WABA phone-list matching, origin-check tightening, WABA-only finish state, stale from field, and SDK script timeout) are addressed in this revision.

The embedded signup flow now validates agent-integration linkage before exchanging the code, verifies the submitted phone-number ID is actually a member of the submitted WABA, and correctly handles all the frontend edge cases called out in previous review rounds. The two remaining comments are minor: a Math.random()-based PIN generator that can be swapped for crypto.randomInt, and an error code declared in the DTO that the implementation never emits.

whatsapp-graph-api.utils.ts for the PIN generator, and send-whatsapp-test-template.dto.ts for the unreachable template_pending_approval error code.

Important Files Changed

Filename Overview
apps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.usecase.ts New use case implementing the full embedded signup flow: feature flag check, agent-integration linkage validation, token exchange, Meta phone/WABA ownership verification, credential save, and webhook configuration. Previously flagged concerns are now addressed.
apps/dashboard/src/components/agents/whatsapp-embedded-signup-button.tsx New Facebook SDK button component with fixed isTrustedFacebookOrigin using URL parsing, FINISH_ONLY_WABA error state, and SDK timeout handling. Previously flagged security and UX issues are addressed.
apps/api/src/app/agents/channels/whatsapp/send-whatsapp-test-template/send-whatsapp-test-template.usecase.ts Now selects sample_order_confirmation with body parameters for Novu-managed credentials, hello_world for manual credentials; error code 131058 added to classifyMetaError. Previously flagged template selection issue is resolved.
apps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.ts Adds token exchange, phone registration, and WABA listing helpers; sendWhatsAppTemplate extended with bodyParameters. PIN generation uses Math.random() rather than a CSPRNG.
apps/api/src/app/integrations/usecases/whatsapp/whatsapp-credentials.utils.ts Adds resolveWhatsAppAppSecret and resolveWhatsAppAppId helpers that fall back to platform env vars for Novu-managed integrations.
apps/api/src/app/agents/channels/whatsapp/configure-whatsapp-webhook/configure-whatsapp-webhook.usecase.ts Now resolves appSecret and appId via the new credential utilities for Novu-managed integrations, falling through to debugAccessToken for the legacy path.
apps/api/src/app/agents/shared/dtos/send-whatsapp-test-template.dto.ts Adds template_pending_approval to the error code enum in the DTO, but this code is never produced by classifyMetaError in the implementation, creating a misleading API contract.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant Dashboard
    participant MetaSDK as Facebook JS SDK
    participant API as Novu API
    participant Meta as Meta Graph API

    User->>Dashboard: Click "Log in with Facebook"
    Dashboard->>MetaSDK: loadFacebookSdk() + FB.login()
    MetaSDK-->>Dashboard: WA_EMBEDDED_SIGNUP postMessage (wabaId, phoneNumberId)
    MetaSDK-->>Dashboard: FB.login callback (authResponse.code)
    Dashboard->>API: POST /integrations/whatsapp/embedded-signup
    API->>API: Feature flag check + agent-integration link validation
    API->>Meta: exchangeEmbeddedSignupCodeForToken (GET /oauth/access_token)
    Meta-->>API: access_token
    API->>Meta: getPhoneNumberDetails (accessToken, phoneNumberId)
    Meta-->>API: display_phone_number
    API->>Meta: listWabaPhoneNumbers (accessToken, wabaId)
    Meta-->>API: phone list - match phoneNumberId
    API->>API: "Save credentials (isNovuManaged=true, from=displayPhone)"
    API->>Meta: registerWhatsAppPhoneNumber (best-effort)
    Meta-->>API: success/failure (warning only)
    API->>Meta: ConfigureWhatsAppWebhook (subscribeWabaMessagesField)
    Meta-->>API: webhook subscribed
    API-->>Dashboard: "success=true, displayPhoneNumber, callbackUrl"
    Dashboard->>User: EmbeddedSignupInboundTestPanel Phase 1 (save subscriber phone)
    User->>Dashboard: Enter personal phone - Save
    Dashboard->>API: PATCH /subscribers/:id (phone)
    Dashboard->>User: Phase 2: wa.me deep link to business number
    User->>Meta: Send WhatsApp message
    Meta->>API: Inbound webhook - ListeningStatus confirmed
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant Dashboard
    participant MetaSDK as Facebook JS SDK
    participant API as Novu API
    participant Meta as Meta Graph API

    User->>Dashboard: Click "Log in with Facebook"
    Dashboard->>MetaSDK: loadFacebookSdk() + FB.login()
    MetaSDK-->>Dashboard: WA_EMBEDDED_SIGNUP postMessage (wabaId, phoneNumberId)
    MetaSDK-->>Dashboard: FB.login callback (authResponse.code)
    Dashboard->>API: POST /integrations/whatsapp/embedded-signup
    API->>API: Feature flag check + agent-integration link validation
    API->>Meta: exchangeEmbeddedSignupCodeForToken (GET /oauth/access_token)
    Meta-->>API: access_token
    API->>Meta: getPhoneNumberDetails (accessToken, phoneNumberId)
    Meta-->>API: display_phone_number
    API->>Meta: listWabaPhoneNumbers (accessToken, wabaId)
    Meta-->>API: phone list - match phoneNumberId
    API->>API: "Save credentials (isNovuManaged=true, from=displayPhone)"
    API->>Meta: registerWhatsAppPhoneNumber (best-effort)
    Meta-->>API: success/failure (warning only)
    API->>Meta: ConfigureWhatsAppWebhook (subscribeWabaMessagesField)
    Meta-->>API: webhook subscribed
    API-->>Dashboard: "success=true, displayPhoneNumber, callbackUrl"
    Dashboard->>User: EmbeddedSignupInboundTestPanel Phase 1 (save subscriber phone)
    User->>Dashboard: Enter personal phone - Save
    Dashboard->>API: PATCH /subscribers/:id (phone)
    Dashboard->>User: Phase 2: wa.me deep link to business number
    User->>Meta: Send WhatsApp message
    Meta->>API: Inbound webhook - ListeningStatus confirmed
Loading

Comments Outside Diff (3)

  1. General comment

    P1 sendWhatsAppTemplate still cannot include bodyParameters in template components

    • Bug
      • The head behavior does not satisfy the claimed contract that sendWhatsAppTemplate supports bodyParameters in outbound template components. In the after run, the script passed bodyParameters ['Alice', 'Code-123'] as the contract input, but the changed graph API utility still has no bodyParameters type/member and no template.components payload construction. The observed after artifact reports hasBodyParametersType=false and payloadIncludesBodyParameters=false, unchanged from base.
    • Cause
      • apps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.ts defines SendTemplateArgs only with accessToken, phoneNumberId, to, templateName, and languageCode, and sendWhatsAppTemplate builds a template object containing only name and language. No optional bodyParameters field is accepted or mapped into Meta template components.
    • Fix
      • Add optional bodyParameters?: string[] to SendTemplateArgs and, when provided, include template.components with a body component whose parameters are text parameters in the same order, e.g. { type: 'body', parameters: bodyParameters.map(text => ({ type: 'text', text })) }. Update the usecase/tests to pass parameters for the template that requires them.

    T-Rex Ran code and verified through T-Rex

  2. apps/api/src/app/agents/channels/whatsapp/send-whatsapp-test-template/send-whatsapp-test-template.usecase.ts, line 15-16 (link)

    P1 Select embedded template

    The updated API contract says Embedded Signup integrations should use sample_order_confirmation, but this use case still sends hello_world for every WhatsApp integration. When the saved credentials are Novu-managed and point at a real business number, Meta rejects hello_world with code 131058, so this test path reports template unavailable instead of completing the connection check. Choose the template from the saved credentials and include the required body parameters for the embedded-signup template.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/api/src/app/agents/channels/whatsapp/send-whatsapp-test-template/send-whatsapp-test-template.usecase.ts
    Line: 15-16
    
    Comment:
    **Select embedded template**
    
    The updated API contract says Embedded Signup integrations should use `sample_order_confirmation`, but this use case still sends `hello_world` for every WhatsApp integration. When the saved credentials are Novu-managed and point at a real business number, Meta rejects `hello_world` with code `131058`, so this test path reports template unavailable instead of completing the connection check. Choose the template from the saved credentials and include the required body parameters for the embedded-signup template.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Cursor

  3. General comment

    P1 WhatsApp template bodyParameters are dropped from outbound Meta payload

    • Bug
      • The changed contract is expected to support bodyParameters, but the current send path ignores provided body parameters. In the after harness run, input args included bodyParameters: ["Alice", "Order 123"], yet the constructed outbound template payload had no template.components body component and includesBodyComponents:false. Templates requiring body placeholders would be sent without required parameters and can be rejected by Meta.
    • Cause
      • apps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.ts defines SendTemplateArgs without a bodyParameters field and sendWhatsAppTemplate builds a fixed payload at lines 239-250 containing only template.name and template.language, with no mapping from body parameters to Meta template.components[].parameters.
    • Fix
      • Add bodyParameters?: string[] (or the agreed DTO shape) to SendTemplateArgs and, when non-empty, include template.components: [{ type: 'body', parameters: bodyParameters.map(text => ({ type: 'text', text })) }] in the Meta Graph payload. Add a unit test that stubs the Meta request and asserts the outbound JSON includes the expected body component.

    T-Rex Ran code and verified through T-Rex

Reviews (4): Last reviewed commit: "Merge branch 'next' into cursor/whatsapp..." | Re-trigger Greptile

scopsy added 4 commits June 17, 2026 11:46
… test

Replace outbound template verification for embedded signup with a phone-save + Open in WhatsApp flow, backed by a new embedded-signup API and Meta login button.
- Delete managed outbound test path (template provisioning, isNovuManaged send-test branch, dead ConnectAndTestPanel props)
- Split 875-line setup guide into setup-guide.tsx / connect-and-test-panel / inbound-test-panel / setup-guide-utils
- Extract useConnectSubscriberPhone hook to eliminate duplicated phone-save logic
- Add ReadOnlyValueRow to setup-guide-primitives
- Fix loadFacebookSdk hang when script tag exists but FB not yet initialized
- Remove unused isNovuManagedWhatsAppIntegration export
@linear-code

linear-code Bot commented Jun 17, 2026

Copy link
Copy Markdown

NV-8062

@netlify

netlify Bot commented Jun 17, 2026

Copy link
Copy Markdown

Deploy Preview for dashboard-v2-novu-staging ready!

Name Link
🔨 Latest commit bde34fa
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/6a3a122331910d00075fa199
😎 Deploy Preview https://deploy-preview-11591.dashboard-v2.novu-staging.co
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements WhatsApp Embedded Signup via Meta Tech Provider. It adds an isNovuManaged credential flag, credential resolution helpers, Meta Graph API utilities for OAuth code exchange and phone registration, a new WhatsAppEmbeddedSignup use case and API endpoint, a Facebook SDK-based login button component, a subscriber phone management hook, and refactors the setup guide to support a feature-flag-driven embedded vs. manual onboarding flow. Existing WhatsApp test template and webhook configuration flows are updated to support Novu-managed credentials with template-specific error handling.

Sequence Diagram

sequenceDiagram
  participant User
  participant WhatsAppSetupGuide
  participant WhatsAppEmbeddedSignupButton
  participant FacebookSDK as Facebook SDK
  participant IntegrationsAPI as POST /integrations/whatsapp/embedded-signup
  participant WhatsAppEmbeddedSignup as WhatsAppEmbeddedSignup Usecase
  participant MetaGraphAPI as Meta Graph API

  rect rgba(30, 100, 200, 0.5)
    note over WhatsAppSetupGuide,WhatsAppEmbeddedSignupButton: Feature flag IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED = true
    User->>WhatsAppSetupGuide: opens WhatsApp setup guide
    WhatsAppSetupGuide->>WhatsAppEmbeddedSignupButton: render "Log in with Facebook"
  end

  rect rgba(200, 100, 30, 0.5)
    note over WhatsAppEmbeddedSignupButton,FacebookSDK: Facebook OAuth + postMessage handshake
    User->>WhatsAppEmbeddedSignupButton: click login button
    WhatsAppEmbeddedSignupButton->>FacebookSDK: loadFacebookSdk() → FB.login(scopes)
    FacebookSDK-->>WhatsAppEmbeddedSignupButton: auth code (response.authResponse.code)
    FacebookSDK-->>WhatsAppEmbeddedSignupButton: wabaId + phoneNumberId (postMessage)
  end

  rect rgba(30, 180, 80, 0.5)
    note over IntegrationsAPI,MetaGraphAPI: Backend embedded signup execution
    WhatsAppEmbeddedSignupButton->>IntegrationsAPI: POST {code, wabaId, phoneNumberId, ...}
    IntegrationsAPI->>WhatsAppEmbeddedSignup: execute(command)
    WhatsAppEmbeddedSignup->>MetaGraphAPI: exchangeEmbeddedSignupCodeForToken(appId, appSecret, code)
    MetaGraphAPI-->>WhatsAppEmbeddedSignup: access_token
    WhatsAppEmbeddedSignup->>MetaGraphAPI: getPhoneNumberDetails(phoneNumberId)
    WhatsAppEmbeddedSignup->>WhatsAppEmbeddedSignup: updateIntegration(credentials, isNovuManaged=true)
    WhatsAppEmbeddedSignup->>MetaGraphAPI: registerWhatsAppPhoneNumber(phoneNumberId, pin)
    WhatsAppEmbeddedSignup->>WhatsAppEmbeddedSignup: configureWhatsAppWebhook()
    WhatsAppEmbeddedSignup-->>IntegrationsAPI: {success, callbackUrl, wabaId, displayPhoneNumber}
    IntegrationsAPI-->>WhatsAppEmbeddedSignupButton: WhatsAppEmbeddedSignupResult
  end

  WhatsAppEmbeddedSignupButton->>WhatsAppSetupGuide: onSuccess()
  WhatsAppSetupGuide->>User: show EmbeddedSignupInboundTestPanel
Loading

Changes

WhatsApp Embedded Signup Feature

Layer / File(s) Summary
Shared data contracts and feature flag
packages/shared/src/entities/integration/credential.interface.ts, libs/dal/src/repositories/integration/integration.schema.ts, packages/shared/src/types/feature-flags.ts, apps/api/src/config/env.validators.ts, apps/dashboard/src/config/index.ts, apps/api/src/.example.env, apps/dashboard/.example.env
Added isNovuManaged flag to ICredentials and Mongoose schema. Registered IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED feature flag with LaunchDarkly/environment variable fallback. Declared three optional env validators for platform credentials and exported dashboard config constants plus isWhatsAppEmbeddedSignupConfigured() helper.
Credential resolution helpers
apps/api/src/app/integrations/usecases/whatsapp/whatsapp-credentials.utils.ts
Added resolveWhatsAppAppSecret() and resolveWhatsAppAppId() to read platform environment variables when isNovuManaged === true, otherwise fall back to stored credential fields, returning undefined when unavailable.
Meta Graph API helpers for embedded signup
apps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.ts
Extended with exchangeEmbeddedSignupCodeForToken() for OAuth code exchange, registerWhatsAppPhoneNumber() for phone registration, and generateWhatsAppRegistrationPin() for 6-digit PIN generation. Updated SendTemplateArgs and sendWhatsAppTemplate() to support optional bodyParameters in template payload.
Embedded signup DTOs and command
apps/api/src/app/integrations/dtos/whatsapp-embedded-signup.dto.ts, apps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.command.ts
Defined WhatsAppEmbeddedSignupRequestDto, WhatsAppEmbeddedSignupFailureDto, and WhatsAppEmbeddedSignupResponseDto with Swagger metadata and validation. Introduced WhatsAppEmbeddedSignupCommand with class-validator constraints on all required string fields.
WhatsApp embedded signup use case
apps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.usecase.ts
Implements end-to-end embedded signup: validates feature flag, checks platform config, loads/validates integration and agent, exchanges auth code for Meta access token, validates phone/waba against Meta, updates credentials with isNovuManaged: true, performs phone registration with PIN, configures webhook, and returns typed responses with optional registration warnings.
API endpoint registration and module wiring
apps/api/src/app/integrations/usecases/index.ts, apps/api/src/app/integrations/integrations.controller.ts, apps/api/src/app/integrations/integrations.module.ts
Registered WhatsAppEmbeddedSignup in use cases. Added POST /integrations/whatsapp/embedded-signup endpoint that maps request to command and invokes use case. Updated IntegrationModule to import AgentsModule via forwardRef.
Updates to existing WhatsApp flows
apps/api/src/app/agents/channels/whatsapp/configure-whatsapp-webhook/configure-whatsapp-webhook.usecase.ts, apps/api/src/app/agents/conversation-runtime/ingress/chat-instance.registry.ts, apps/api/src/app/agents/channels/whatsapp/send-whatsapp-test-template/send-whatsapp-test-template.usecase.ts, apps/api/src/app/agents/channels/whatsapp/send-whatsapp-test-template/send-whatsapp-test-template.usecase.spec.ts, apps/api/src/app/agents/shared/dtos/send-whatsapp-test-template.dto.ts, apps/api/src/app/agents/channels/integrations/agent-integrations.controller.ts, apps/dashboard/src/api/agents.ts
Updated webhook configuration to use resolved credential helpers and emit isNovuManaged-conditional messages. Updated chat registry to validate and pass resolved appSecret. Extended test template sending to select hello_world (manual) or sample_order_confirmation (Novu-managed) with body parameters. Added Meta error code 131058 handling and revised template_unavailable messages to direct users to Embedded Signup. Added template_pending_approval error code and updated test assertions.
Dashboard API client and React hooks
apps/dashboard/src/api/integrations.ts, apps/dashboard/src/api/agents.ts, apps/dashboard/src/hooks/use-whatsapp-embedded-signup.ts, apps/dashboard/src/hooks/use-connect-subscriber-phone.ts
Added WhatsApp embedded signup API types and completeWhatsAppEmbeddedSignup() function. Extended error codes. Introduced useWhatsAppEmbeddedSignup mutation hook for completion flow. Added useConnectSubscriberPhone hook for phone validation, saving, and subscriber cache invalidation.
Setup guide utilities and UI primitives
apps/dashboard/src/components/agents/whatsapp-setup-guide-utils.ts, apps/dashboard/src/components/agents/setup-guide-primitives.tsx, apps/dashboard/src/components/agents/setup-guide-step-utils.ts
Added PHONE_PATTERN regex, buildAgentWebhookUrl(), and buildWhatsAppDeepLink() utilities. Introduced ReadOnlyValueRow component for read-only value display with copy button. Updated hasWhatsAppUserCredentials() to conditionally validate only required fields when credentials.isNovuManaged === true.
Facebook SDK integration and embedded signup button
apps/dashboard/src/components/agents/whatsapp-embedded-signup-button.tsx
Implemented WhatsAppEmbeddedSignupButton component with singleton Facebook SDK loader, OAuth login flow, postMessage event listener for session data capture, signup completion with error/success handling, phone registration warnings, and integration refetch.
Webhook configuration and test template flow
apps/dashboard/src/components/agents/whatsapp-connect-and-test-panel.tsx
Introduced ConnectAndTestPanel component managing auto webhook configuration with manual fallback, subscriber phone validation, and test template sending with success/error guidance. Added ManualWebhookFallback subcomponent for manual webhook registration instructions.
Embedded signup inbound test panel
apps/dashboard/src/components/agents/whatsapp-embedded-signup-inbound-test-panel.tsx
Introduced EmbeddedSignupInboundTestPanel component for WhatsApp inbound confirmation: derives business phone, computes deep link, manages phone input, displays read-only business number with "Open in WhatsApp" button when saved.
Setup guide refactor with embedded signup flow
apps/dashboard/src/components/agents/whatsapp-setup-guide.tsx
Refactored setup guide to use extracted components and utilities. Added feature-flag-driven embedded vs. manual credentials flow toggle. Adjusted step progression, computed embedded-specific listening message, conditionally rendered embedded login or manual entry, and displayed inbound test panel upon connection.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • novuhq/novu#11059: Implements SendWhatsAppTestTemplate use case and DTOs that this PR extends with new Meta error codes and embedded template support.
  • novuhq/novu#11061: Modifies hasWhatsAppUserCredentials credential validation logic that this PR updates to handle Novu-managed credential requirements.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits format with valid type (feat), valid scopes (dashboard,api-service), imperative description, and Linear ticket reference (NV-8062).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

LaunchDarkly flag references

🔍 1 flag added or modified

Name Key Aliases found Info
IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED

Comment thread apps/api/src/app/integrations/integrations.controller.ts
phoneRegistrationWarning,
};
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Agentic Security Review
Severity: MEDIUM
The server persists wabaId and phoneNumberId from client-provided input directly into credentials (businessAccountId / phoneNumberIdentification) without enforcing that these identifiers are authoritatively bound to the exchanged Meta token before storage.

Impact: forged or mismatched identifiers can be persisted, causing WhatsApp integration misbinding and webhook/registration operations against unintended assets.

Fix in Cursor Fix in Web

Reviewed by Cursor Security Reviewer for commit 5d2ec6f. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cursoragent review this comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking a look!

Open in Web Open in Cursor 


/**
* For WhatsApp Business agent integrations Novu manages the webhook Verify
* Token automatically: it's just a shared secret echoed back to Meta during

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Agentic Security Review
Severity: HIGH
isNovuManaged is used as a trust switch to select deployment-level Meta app credentials from environment variables. Because this flag is persisted in integration credentials and treated as authoritative here, any path that can set that credential key can escalate to platform-managed app context.

Impact: tenant-triggered flows can execute Meta app subscription/configuration logic with shared platform credentials, expanding blast radius beyond the tenant-scoped credential model.

Fix in Cursor Fix in Web

Reviewed by Cursor Security Reviewer for commit 5d2ec6f. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cursoragent review this comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking a look!

Open in Web Open in Cursor 

@cursor cursor Bot requested a review from ChmaraX June 17, 2026 10:11

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Risk: high. Cursor Security Agent completed with unresolved HIGH/MEDIUM auth and credential-binding findings that need human review before merge. Cursor Bugbot was not present on this PR.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver

@cursor cursor Bot requested a review from djabarovgeorge June 17, 2026 10:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (2)
apps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.ts (1)

254-257: ⚡ Quick win

Prefer interface for backend type definitions in this file.

These newly added backend response shapes should use interface instead of type to match repo conventions.

As per coding guidelines, on backend **/*.{ts,tsx} files should use interface for type definitions.

Also applies to: 278-280

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.ts`
around lines 254 - 257, The ExchangeCodeForTokenResponse type definition and the
other type definition at lines 278-280 should use interface instead of type to
follow backend coding conventions. Convert these type alias definitions to
interface declarations, replacing the intersection operator (&) with the extends
keyword to inherit from MetaErrorBody or other base types. This applies to all
backend response shape type definitions in this file.

Source: Coding guidelines

apps/api/src/app/integrations/dtos/whatsapp-embedded-signup.dto.ts (1)

36-46: ⚡ Quick win

Use interface instead of type for backend failure shape

Please convert WhatsAppEmbeddedSignupFailure to an interface to match backend typing conventions.

Proposed change
-export type WhatsAppEmbeddedSignupFailure = {
+export interface WhatsAppEmbeddedSignupFailure {
   code:
     | 'feature_disabled'
     | 'missing_platform_config'
     | 'token_exchange_failed'
     | 'integration_not_found'
     | 'phone_registration_failed'
     | 'webhook_configuration_failed'
     | 'unknown';
   message: string;
-};
+}

As per coding guidelines, **/*.{ts,tsx} on the backend should use interface for type definitions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/app/integrations/dtos/whatsapp-embedded-signup.dto.ts` around
lines 36 - 46, Convert the WhatsAppEmbeddedSignupFailure from a type alias to an
interface definition. Replace the `type` keyword with `interface` and maintain
the same structure with the code and message properties. This aligns with
backend TypeScript conventions where interfaces are preferred over type aliases
for defining object shapes.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/.example.env`:
- Around line 67-68: The .example.env file documents the
IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED flag but is missing the required WhatsApp
API credential environment variables NOVU_WHATSAPP_APP_ID and
NOVU_WHATSAPP_APP_SECRET. Add these two missing environment variable entries to
the example file near the IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED flag so that
developers configuring WhatsApp embedded signup have all necessary variables
documented and can avoid silent misconfiguration in self-hosted setups.

In
`@apps/api/src/app/integrations/usecases/whatsapp/whatsapp-credentials.utils.ts`:
- Around line 16-24: The resolveWhatsAppAppId function currently returns
undefined for all non-Novu-managed integrations, which discards existing stored
app ID credentials. Modify the function to preserve the app ID fallback by
returning the app ID from the credentials object itself when
credentials.isNovuManaged is not true. This ensures that manually managed
WhatsApp integrations retain their configured app ID instead of losing it to
undefined.

In
`@apps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.usecase.ts`:
- Around line 126-127: After trimming phoneNumberId and wabaId using the trim()
method, add validation checks to ensure they are not empty strings before
persisting credentials. If either phoneNumberId or wabaId becomes an empty
string after trimming (indicating whitespace-only input), throw an appropriate
error immediately. This validation should occur right after the trim operations
on lines 126-127, before the credentials are persisted to the database at lines
148-149, to implement fail-fast input validation.
- Around line 130-137: The phoneDetails.body property returned by
getPhoneNumberDetails is a union type (PhoneNumberDetailsResponse |
MetaErrorBody), but the code accesses display_phone_number without narrowing the
type. While extractMetaError provides runtime validation, TypeScript requires an
explicit type guard. Replace the current condition check before accessing
phoneDetails.body.display_phone_number with a type predicate that explicitly
narrows the union, such as checking if display_phone_number exists as a property
in the body object using the in operator or by using a type guard function that
checks the body structure directly before accessing the display_phone_number
property.

In `@apps/dashboard/.example.env`:
- Around line 6-7: The .example.env file is missing documentation for the
WhatsApp embedded signup configuration identifiers that the dashboard now
requires. Add the two missing environment variables VITE_NOVU_WHATSAPP_APP_ID
and VITE_NOVU_WHATSAPP_CONFIG_ID to the .example.env file alongside the existing
VITE_IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED flag, ensuring they are commented out
as examples and include brief descriptions of their purpose for the embedded
signup flow.

In `@apps/dashboard/src/components/agents/whatsapp-connect-and-test-panel.tsx`:
- Around line 80-87: The useEffect hook that handles phone initialization in the
whatsapp-connect-and-test-panel component only sets the phone state when a saved
phone exists, but does not clear it when there is no saved phone, causing stale
values to persist. Modify the effect to add an else clause that explicitly
clears the phone state (sets it to an empty string) when savedPhone is empty or
falsy, ensuring the input is properly reset when switching between subscribers
or when the subscriber lacks a phone number.

In `@apps/dashboard/src/components/agents/whatsapp-embedded-signup-button.tsx`:
- Around line 86-87: The origin validation in the postMessage event handler uses
endsWith() which is too permissive and allows spoofed domains like
evilfacebook.com to pass the check. Replace the endsWith('facebook.com') check
in the conditional statement with a more strict validation that either matches
the exact origin against a whitelist of legitimate Facebook/Meta domains or uses
a more precise string matching approach that prevents substring spoofing. This
hardened check should only accept messages from explicitly allowed origins to
prevent untrusted windows from injecting WA_EMBEDDED_SIGNUP payloads.

In `@apps/dashboard/src/config/index.ts`:
- Around line 101-103: The isWhatsAppEmbeddedSignupConfigured() function
currently only checks if NOVU_WHATSAPP_APP_ID and NOVU_WHATSAPP_CONFIG_ID are
truthy, which returns true for whitespace-only strings like "   ". Update the
function to additionally validate that both environment variables contain actual
non-whitespace content, not just whitespace characters, so that invalid
configurations are properly rejected.

In `@apps/dashboard/src/hooks/use-connect-subscriber-phone.ts`:
- Around line 35-40: In the useEffect hook with dependencies [savedPhone,
subscriberId], the current logic only sets the phone state when savedPhone is
truthy, which allows a previous subscriber's phone to persist when switching to
a subscriber with no saved phone. Add an else clause to the if statement that
resets the phone state (by calling setPhone with an empty string or appropriate
default value) when savedPhone is falsy, ensuring the phone state is properly
cleared when the subscriber changes and has no saved phone.

---

Nitpick comments:
In `@apps/api/src/app/integrations/dtos/whatsapp-embedded-signup.dto.ts`:
- Around line 36-46: Convert the WhatsAppEmbeddedSignupFailure from a type alias
to an interface definition. Replace the `type` keyword with `interface` and
maintain the same structure with the code and message properties. This aligns
with backend TypeScript conventions where interfaces are preferred over type
aliases for defining object shapes.

In `@apps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.ts`:
- Around line 254-257: The ExchangeCodeForTokenResponse type definition and the
other type definition at lines 278-280 should use interface instead of type to
follow backend coding conventions. Convert these type alias definitions to
interface declarations, replacing the intersection operator (&) with the extends
keyword to inherit from MetaErrorBody or other base types. This applies to all
backend response shape type definitions in this file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: aabc9b83-b481-4a8f-96b5-a2fafc7cfca7

📥 Commits

Reviewing files that changed from the base of the PR and between 0f5c19b and 5d2ec6f.

📒 Files selected for processing (32)
  • apps/api/src/.example.env
  • apps/api/src/app/agents/channels/integrations/agent-integrations.controller.ts
  • apps/api/src/app/agents/channels/whatsapp/configure-whatsapp-webhook/configure-whatsapp-webhook.usecase.ts
  • apps/api/src/app/agents/channels/whatsapp/send-whatsapp-test-template/send-whatsapp-test-template.usecase.spec.ts
  • apps/api/src/app/agents/channels/whatsapp/send-whatsapp-test-template/send-whatsapp-test-template.usecase.ts
  • apps/api/src/app/agents/conversation-runtime/ingress/chat-instance.registry.ts
  • apps/api/src/app/agents/shared/dtos/send-whatsapp-test-template.dto.ts
  • apps/api/src/app/integrations/dtos/whatsapp-embedded-signup.dto.ts
  • apps/api/src/app/integrations/integrations.controller.ts
  • apps/api/src/app/integrations/integrations.module.ts
  • apps/api/src/app/integrations/usecases/index.ts
  • apps/api/src/app/integrations/usecases/whatsapp/whatsapp-credentials.utils.ts
  • apps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.command.ts
  • apps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.usecase.ts
  • apps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.ts
  • apps/api/src/config/env.validators.ts
  • apps/dashboard/.example.env
  • apps/dashboard/src/api/agents.ts
  • apps/dashboard/src/api/integrations.ts
  • apps/dashboard/src/components/agents/setup-guide-primitives.tsx
  • apps/dashboard/src/components/agents/setup-guide-step-utils.ts
  • apps/dashboard/src/components/agents/whatsapp-connect-and-test-panel.tsx
  • apps/dashboard/src/components/agents/whatsapp-embedded-signup-button.tsx
  • apps/dashboard/src/components/agents/whatsapp-embedded-signup-inbound-test-panel.tsx
  • apps/dashboard/src/components/agents/whatsapp-setup-guide-utils.ts
  • apps/dashboard/src/components/agents/whatsapp-setup-guide.tsx
  • apps/dashboard/src/config/index.ts
  • apps/dashboard/src/hooks/use-connect-subscriber-phone.ts
  • apps/dashboard/src/hooks/use-whatsapp-embedded-signup.ts
  • libs/dal/src/repositories/integration/integration.schema.ts
  • packages/shared/src/entities/integration/credential.interface.ts
  • packages/shared/src/types/feature-flags.ts

Comment thread apps/api/src/.example.env
Comment on lines +67 to +68
# WhatsApp Embedded Signup (Meta Tech Provider). Process-env fallback when LaunchDarkly is unset.
# IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED=true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document the new API WhatsApp env vars in the example file.

Lines 67-68 add the flag toggle, but the API example still omits NOVU_WHATSAPP_APP_ID and NOVU_WHATSAPP_APP_SECRET, which are required for the embedded-signup backend path. This can lead to silent misconfiguration in self-hosted setups.

Suggested diff
 # WhatsApp Embedded Signup (Meta Tech Provider). Process-env fallback when LaunchDarkly is unset.
 # IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED=true
+NOVU_WHATSAPP_APP_ID=
+NOVU_WHATSAPP_APP_SECRET=
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/.example.env` around lines 67 - 68, The .example.env file
documents the IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED flag but is missing the
required WhatsApp API credential environment variables NOVU_WHATSAPP_APP_ID and
NOVU_WHATSAPP_APP_SECRET. Add these two missing environment variable entries to
the example file near the IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED flag so that
developers configuring WhatsApp embedded signup have all necessary variables
documented and can avoid silent misconfiguration in self-hosted setups.

Comment on lines +16 to +24
export function resolveWhatsAppAppId(credentials: ICredentials): string | undefined {
if (credentials.isNovuManaged === true) {
const platformAppId = process.env.NOVU_WHATSAPP_APP_ID?.trim();

return platformAppId || undefined;
}

return undefined;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve manual-credentials app ID fallback in resolveWhatsAppAppId.

resolveWhatsAppAppId currently returns undefined for all non-Novu-managed integrations. That drops existing stored app-id credentials and can break non-embedded WhatsApp paths.

Suggested diff
 export function resolveWhatsAppAppId(credentials: ICredentials): string | undefined {
   if (credentials.isNovuManaged === true) {
     const platformAppId = process.env.NOVU_WHATSAPP_APP_ID?.trim();

     return platformAppId || undefined;
   }

-  return undefined;
+  const storedAppId = typeof credentials.appID === 'string' ? credentials.appID.trim() : '';
+
+  return storedAppId || undefined;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function resolveWhatsAppAppId(credentials: ICredentials): string | undefined {
if (credentials.isNovuManaged === true) {
const platformAppId = process.env.NOVU_WHATSAPP_APP_ID?.trim();
return platformAppId || undefined;
}
return undefined;
}
export function resolveWhatsAppAppId(credentials: ICredentials): string | undefined {
if (credentials.isNovuManaged === true) {
const platformAppId = process.env.NOVU_WHATSAPP_APP_ID?.trim();
return platformAppId || undefined;
}
const storedAppId = typeof credentials.appID === 'string' ? credentials.appID.trim() : '';
return storedAppId || undefined;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/api/src/app/integrations/usecases/whatsapp/whatsapp-credentials.utils.ts`
around lines 16 - 24, The resolveWhatsAppAppId function currently returns
undefined for all non-Novu-managed integrations, which discards existing stored
app ID credentials. Modify the function to preserve the app ID fallback by
returning the app ID from the credentials object itself when
credentials.isNovuManaged is not true. This ensures that manually managed
WhatsApp integrations retain their configured app ID instead of losing it to
undefined.

Comment on lines +126 to +127
const phoneNumberId = command.phoneNumberId.trim();
const wabaId = command.wabaId.trim();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject whitespace-only phoneNumberId / wabaId before persisting credentials.

After trimming, whitespace-only inputs become empty strings and still flow into saved credentials (Line 148-149). Fail fast before update.

💡 Suggested fix
-import { ForbiddenException, Injectable, NotFoundException } from '`@nestjs/common`';
+import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '`@nestjs/common`';
@@
     const phoneNumberId = command.phoneNumberId.trim();
     const wabaId = command.wabaId.trim();
+    if (!phoneNumberId || !wabaId) {
+      throw new BadRequestException('phoneNumberId and wabaId must be non-empty values.');
+    }

As per coding guidelines, apps/api/** changes should include proper input validation.

Also applies to: 145-150

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.usecase.ts`
around lines 126 - 127, After trimming phoneNumberId and wabaId using the trim()
method, add validation checks to ensure they are not empty strings before
persisting credentials. If either phoneNumberId or wabaId becomes an empty
string after trimming (indicating whitespace-only input), throw an appropriate
error immediately. This validation should occur right after the trim operations
on lines 126-127, before the credentials are persisted to the database at lines
148-149, to implement fail-fast input validation.

Source: Coding guidelines

Comment on lines +6 to +7
# WhatsApp Embedded Signup (Meta Tech Provider). Vite env fallback when LaunchDarkly client is unset.
# VITE_IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED=true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add missing dashboard env examples for embedded signup identifiers.

The flag is documented here, but VITE_NOVU_WHATSAPP_APP_ID and VITE_NOVU_WHATSAPP_CONFIG_ID are missing, even though the dashboard now depends on them for enabling the embedded flow.

Suggested diff
 # WhatsApp Embedded Signup (Meta Tech Provider). Vite env fallback when LaunchDarkly client is unset.
 # VITE_IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED=true
+VITE_NOVU_WHATSAPP_APP_ID=
+VITE_NOVU_WHATSAPP_CONFIG_ID=
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# WhatsApp Embedded Signup (Meta Tech Provider). Vite env fallback when LaunchDarkly client is unset.
# VITE_IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED=true
# WhatsApp Embedded Signup (Meta Tech Provider). Vite env fallback when LaunchDarkly client is unset.
# VITE_IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED=true
VITE_NOVU_WHATSAPP_APP_ID=
VITE_NOVU_WHATSAPP_CONFIG_ID=
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/.example.env` around lines 6 - 7, The .example.env file is
missing documentation for the WhatsApp embedded signup configuration identifiers
that the dashboard now requires. Add the two missing environment variables
VITE_NOVU_WHATSAPP_APP_ID and VITE_NOVU_WHATSAPP_CONFIG_ID to the .example.env
file alongside the existing VITE_IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED flag,
ensuring they are commented out as examples and include brief descriptions of
their purpose for the embedded signup flow.

Comment thread apps/dashboard/src/components/agents/whatsapp-embedded-signup-button.tsx Outdated
Comment thread apps/dashboard/src/config/index.ts
Comment thread apps/dashboard/src/hooks/use-connect-subscriber-phone.ts
Comment thread apps/dashboard/src/components/agents/whatsapp-embedded-signup-button.tsx Outdated
Comment thread apps/dashboard/src/components/agents/whatsapp-embedded-signup-button.tsx Outdated
@scopsy

scopsy commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

@cursoragent fix the api build failure

@cursor

cursor Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Fixed the API build failure.

Cause: In whatsapp-embedded-signup.usecase.ts, getPhoneNumberDetails() returns body typed as PhoneNumberDetailsResponse | MetaErrorBody. TypeScript couldn't guarantee display_phone_number exists on that union, even after the Meta error check.

Fix: After confirming there's no Meta error, cast the body to PhoneNumberDetailsResponse before reading display_phone_number — same pattern as whatsapp-validate-token.usecase.ts.

The API build now completes successfully (pnpm --filter @novu/api-service build). The fix is committed and pushed to cursor/whatsapp-embedded-signup-inbound-test.

Open in Web Open in Cursor 

Cast getPhoneNumberDetails body to PhoneNumberDetailsResponse after
Meta error checks, matching whatsapp-validate-token.usecase pattern.

Co-authored-by: Dima Grossman <dima@grossman.io>
…mbedded signup

- Validate agent-integration linkage before token exchange
- Require Meta phone/WABA ownership verification before saving credentials
- Clear stale credentials.from on reconnect when display phone is unavailable
- Harden postMessage origin check against subdomain spoofing (CodeQL)
- Handle FINISH_ONLY_WABA and stale Facebook SDK init with timeout/errors
- Reset subscriber phone state when switching subscribers or integrations

Co-authored-by: Dima Grossman <dima@grossman.io>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.usecase.ts`:
- Around line 197-214: The code on line 213 attempts to call .find() on
wabaPhones.body.data without verifying it exists first, even though the
WabaPhoneNumbersResponse type defines data as optional. Add an explicit guard
check after the existing error validation to verify that wabaPhones.body.data
exists and is an array before attempting to find the matched phone. If data is
missing or undefined, return an error response with an appropriate message
indicating that no phone numbers were returned from the Meta API.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 02194a45-44cf-4409-9901-3a7b5da1f2e3

📥 Commits

Reviewing files that changed from the base of the PR and between 1f8dc20 and 7d56edf.

📒 Files selected for processing (6)
  • apps/api/src/app/integrations/dtos/whatsapp-embedded-signup.dto.ts
  • apps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.usecase.ts
  • apps/dashboard/src/components/agents/whatsapp-connect-and-test-panel.tsx
  • apps/dashboard/src/components/agents/whatsapp-embedded-signup-button.tsx
  • apps/dashboard/src/config/index.ts
  • apps/dashboard/src/hooks/use-connect-subscriber-phone.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/dashboard/src/config/index.ts
  • apps/dashboard/src/components/agents/whatsapp-connect-and-test-panel.tsx
  • apps/dashboard/src/components/agents/whatsapp-embedded-signup-button.tsx
  • apps/api/src/app/integrations/dtos/whatsapp-embedded-signup.dto.ts

@scopsy

scopsy commented Jun 21, 2026

Copy link
Copy Markdown
Contributor Author

@cursoragent review pr comments and only address if needed

@cursor

cursor Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Reviewed the PR comments against current code. Most were already addressed in 7d56edf; I only fixed what was still outstanding.

Already addressed (no changes needed)

  • Agent linkage check before token exchange
  • Meta ownership validation before saving credentials
  • postMessage origin hardening (CodeQL)
  • WABA-only finish error handling
  • Stale SDK script timeout
  • Clear stale credentials.from on reconnect
  • Phone state reset in hooks
  • Config trim for whitespace-only env vars

Fixed in this commit (117f329)

  1. Embedded test template (Greptile P1) — SendWhatsAppTestTemplate now sends sample_order_confirmation with body parameters for isNovuManaged integrations instead of always using hello_world (which Meta rejects on real business numbers with error 131058).
  2. bodyParameters in Meta payload (Greptile P1) — sendWhatsAppTemplate now maps bodyParameters into template.components when provided.
  3. WABA phone list guard (CodeRabbit) — Returns a clear error when Meta omits the phone number list instead of treating it as a mismatch.

Skipped (not needed)

  • Docstring coverage warning — pre-merge advisory, not a functional blocker.
  • Dashboard hello_world copy in ConnectAndTestPanel — that panel is only used on the manual credentials path; embedded signup uses EmbeddedSignupInboundTestPanel with inbound webhook confirmation.

Build and unit tests pass. Changes pushed to the branch.

Open in Web Open in Cursor 

- Add bodyParameters support to sendWhatsAppTemplate Meta payload
- Use sample_order_confirmation with body params for isNovuManaged integrations
- Guard missing WABA phone list data before matching phone number id

Co-authored-by: Dima Grossman <dima@grossman.io>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.ts (1)

316-318: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use cryptographically secure random for PIN generation.

Math.random() is not cryptographically secure and produces predictable values. For a registration PIN used to authorize phone number ownership with WhatsApp, an attacker could potentially predict the PIN during the registration window. Use Node.js crypto.randomInt() instead.

🔒 Proposed fix using crypto.randomInt
+import { randomInt } from 'node:crypto';
+
 export function generateWhatsAppRegistrationPin(): string {
-  return Math.floor(100000 + Math.random() * 900000).toString();
+  return randomInt(100000, 1000000).toString();
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.ts`
around lines 316 - 318, The generateWhatsAppRegistrationPin function currently
uses Math.random() to generate the PIN, which is not cryptographically secure
and could be predictable to attackers. Replace the Math.floor(100_000 +
Math.random() * 900_000) calculation with Node.js crypto.randomInt() to generate
a secure random integer between 100000 and 999999 (inclusive). You will need to
import crypto.randomInt at the top of the file if not already imported. This
ensures the registration PIN cannot be easily predicted during the authorization
window.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@apps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.ts`:
- Around line 316-318: The generateWhatsAppRegistrationPin function currently
uses Math.random() to generate the PIN, which is not cryptographically secure
and could be predictable to attackers. Replace the Math.floor(100_000 +
Math.random() * 900_000) calculation with Node.js crypto.randomInt() to generate
a secure random integer between 100000 and 999999 (inclusive). You will need to
import crypto.randomInt at the top of the file if not already imported. This
ensures the registration PIN cannot be easily predicted during the authorization
window.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 100f7bd5-45f0-4e5b-94ac-53490e6ebef7

📥 Commits

Reviewing files that changed from the base of the PR and between 7d56edf and 117f329.

📒 Files selected for processing (4)
  • apps/api/src/app/agents/channels/whatsapp/send-whatsapp-test-template/send-whatsapp-test-template.usecase.spec.ts
  • apps/api/src/app/agents/channels/whatsapp/send-whatsapp-test-template/send-whatsapp-test-template.usecase.ts
  • apps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.usecase.ts
  • apps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.usecase.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants