feat(dashboard,api-service): WhatsApp Embedded Signup with inbound connection test fixes NV-8062#11591
feat(dashboard,api-service): WhatsApp Embedded Signup with inbound connection test fixes NV-8062#11591scopsy wants to merge 9 commits into
Conversation
… 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
✅ Deploy Preview for dashboard-v2-novu-staging ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR implements WhatsApp Embedded Signup via Meta Tech Provider. It adds an Sequence DiagramsequenceDiagram
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
ChangesWhatsApp Embedded Signup Feature
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ 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. Comment |
LaunchDarkly flag references🔍 1 flag added or modified
|
| phoneRegistrationWarning, | ||
| }; | ||
| } | ||
| } |
There was a problem hiding this comment.
🔒 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.
Reviewed by Cursor Security Reviewer for commit 5d2ec6f. Configure here.
|
|
||
| /** | ||
| * For WhatsApp Business agent integrations Novu manages the webhook Verify | ||
| * Token automatically: it's just a shared secret echoed back to Meta during |
There was a problem hiding this comment.
🔒 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.
Reviewed by Cursor Security Reviewer for commit 5d2ec6f. Configure here.
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (2)
apps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.ts (1)
254-257: ⚡ Quick winPrefer
interfacefor backend type definitions in this file.These newly added backend response shapes should use
interfaceinstead oftypeto match repo conventions.As per coding guidelines, on backend
**/*.{ts,tsx}files should useinterfacefor 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 winUse
interfaceinstead oftypefor backend failure shapePlease convert
WhatsAppEmbeddedSignupFailureto aninterfaceto 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 useinterfacefor 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
📒 Files selected for processing (32)
apps/api/src/.example.envapps/api/src/app/agents/channels/integrations/agent-integrations.controller.tsapps/api/src/app/agents/channels/whatsapp/configure-whatsapp-webhook/configure-whatsapp-webhook.usecase.tsapps/api/src/app/agents/channels/whatsapp/send-whatsapp-test-template/send-whatsapp-test-template.usecase.spec.tsapps/api/src/app/agents/channels/whatsapp/send-whatsapp-test-template/send-whatsapp-test-template.usecase.tsapps/api/src/app/agents/conversation-runtime/ingress/chat-instance.registry.tsapps/api/src/app/agents/shared/dtos/send-whatsapp-test-template.dto.tsapps/api/src/app/integrations/dtos/whatsapp-embedded-signup.dto.tsapps/api/src/app/integrations/integrations.controller.tsapps/api/src/app/integrations/integrations.module.tsapps/api/src/app/integrations/usecases/index.tsapps/api/src/app/integrations/usecases/whatsapp/whatsapp-credentials.utils.tsapps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.command.tsapps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.usecase.tsapps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.tsapps/api/src/config/env.validators.tsapps/dashboard/.example.envapps/dashboard/src/api/agents.tsapps/dashboard/src/api/integrations.tsapps/dashboard/src/components/agents/setup-guide-primitives.tsxapps/dashboard/src/components/agents/setup-guide-step-utils.tsapps/dashboard/src/components/agents/whatsapp-connect-and-test-panel.tsxapps/dashboard/src/components/agents/whatsapp-embedded-signup-button.tsxapps/dashboard/src/components/agents/whatsapp-embedded-signup-inbound-test-panel.tsxapps/dashboard/src/components/agents/whatsapp-setup-guide-utils.tsapps/dashboard/src/components/agents/whatsapp-setup-guide.tsxapps/dashboard/src/config/index.tsapps/dashboard/src/hooks/use-connect-subscriber-phone.tsapps/dashboard/src/hooks/use-whatsapp-embedded-signup.tslibs/dal/src/repositories/integration/integration.schema.tspackages/shared/src/entities/integration/credential.interface.tspackages/shared/src/types/feature-flags.ts
| # WhatsApp Embedded Signup (Meta Tech Provider). Process-env fallback when LaunchDarkly is unset. | ||
| # IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED=true |
There was a problem hiding this comment.
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.
| 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; | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| const phoneNumberId = command.phoneNumberId.trim(); | ||
| const wabaId = command.wabaId.trim(); |
There was a problem hiding this comment.
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
| # WhatsApp Embedded Signup (Meta Tech Provider). Vite env fallback when LaunchDarkly client is unset. | ||
| # VITE_IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED=true |
There was a problem hiding this comment.
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.
| # 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.
|
@cursoragent fix the api build failure |
|
Fixed the API build failure. Cause: In Fix: After confirming there's no Meta error, cast the body to The API build now completes successfully ( |
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>
There was a problem hiding this comment.
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
📒 Files selected for processing (6)
apps/api/src/app/integrations/dtos/whatsapp-embedded-signup.dto.tsapps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.usecase.tsapps/dashboard/src/components/agents/whatsapp-connect-and-test-panel.tsxapps/dashboard/src/components/agents/whatsapp-embedded-signup-button.tsxapps/dashboard/src/config/index.tsapps/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
|
@cursoragent review pr comments and only address if needed |
|
Reviewed the PR comments against current code. Most were already addressed in Already addressed (no changes needed)
Fixed in this commit (
|
- 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>
There was a problem hiding this comment.
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 winUse 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.jscrypto.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
📒 Files selected for processing (4)
apps/api/src/app/agents/channels/whatsapp/send-whatsapp-test-template/send-whatsapp-test-template.usecase.spec.tsapps/api/src/app/agents/channels/whatsapp/send-whatsapp-test-template/send-whatsapp-test-template.usecase.tsapps/api/src/app/integrations/usecases/whatsapp/whatsapp-embedded-signup.usecase.tsapps/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





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
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 withbodyParametersand improves Meta error handling.isNovuManagedto the integration credentials schema.Key technical decisions
isNovuManaged: trueand derives WhatsApp App ID/secret from platform environment variables for webhook/chat adapter configuration.fromset to the business display phone, and updates WhatsApp test-template selection tosample_order_confirmationwith required body parameters.Testing
bodyParametersfor 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 asisNovuManaged: true, registers the phone number with Meta, and auto-configures the webhookresolveWhatsAppAppSecret/resolveWhatsAppAppIdhelpers inwhatsapp-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 SecretsendWhatsAppTemplateextended withbodyParameterssupport for future use131058classified inclassifyMetaError(test number restriction)IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLEDDashboard (
apps/dashboard)WhatsAppEmbeddedSignupButton— brand-accurate Meta Facebook login button (#1877F2) that drives the FB SDK OAuth popup and posts the result to the signup endpointEmbeddedSignupInboundTestPanel— two-phase panel: (1) save subscriber phone, (2) show business number + "Open in WhatsApp" deep link; connection confirmed when first inbound webhook arrives viaListeningStatusConnectAndTestPanelextracted to its own file; deadskipConnectStep/usesMetaSampleTemplateprops andTestStatus.pendingstate removeduseConnectSubscriberPhonehook — shared subscriber phone save logic (validate → patch → invalidate)ReadOnlyValueRowpromoted tosetup-guide-primitiveswhatsapp-setup-guide.tsxsplit from 875 lines into focused modulesConnection flow
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
IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLED+NOVU_WHATSAPP_APP_ID/NOVU_WHATSAPP_APP_SECRET/NOVU_WHATSAPP_CONFIG_IDmust all be set). Manual credentials path is completely unaffected.credentials.fromis 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: truecredentials, 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.POST /integrations/whatsapp/embedded-signupuse case with full agent-integration linkage validation and Meta ownership checks;resolveWhatsAppAppSecret/resolveWhatsAppAppIdhelpers allow Novu-managed integrations to skip per-user App Secret storage;sendWhatsAppTemplateextended withbodyParametersand the test-template use case now selectssample_order_confirmation+ body params for Novu-managed credentials.WhatsAppEmbeddedSignupButtondrives the FB SDK OAuth popup with a fixedisTrustedFacebookOrigincheck,EmbeddedSignupInboundTestPanelhandles the two-phase "save phone → wa.me deep link" UX; the existingwhatsapp-setup-guide.tsxis refactored from 875 lines into focused modules.isNovuManagedfield added to the credential interface and integration schema;IS_WHATSAPP_EMBEDDED_SIGNUP_ENABLEDfeature 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
fromfield, 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 forcrypto.randomInt, and an error code declared in the DTO that the implementation never emits.whatsapp-graph-api.utils.tsfor the PIN generator, andsend-whatsapp-test-template.dto.tsfor the unreachabletemplate_pending_approvalerror code.Important Files Changed
isTrustedFacebookOriginusing URL parsing,FINISH_ONLY_WABAerror state, and SDK timeout handling. Previously flagged security and UX issues are addressed.sample_order_confirmationwith body parameters for Novu-managed credentials,hello_worldfor manual credentials; error code 131058 added toclassifyMetaError. Previously flagged template selection issue is resolved.sendWhatsAppTemplateextended withbodyParameters. PIN generation usesMath.random()rather than a CSPRNG.resolveWhatsAppAppSecretandresolveWhatsAppAppIdhelpers that fall back to platform env vars for Novu-managed integrations.appSecretandappIdvia the new credential utilities for Novu-managed integrations, falling through todebugAccessTokenfor the legacy path.template_pending_approvalto the error code enum in the DTO, but this code is never produced byclassifyMetaErrorin 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%%{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 confirmedComments Outside Diff (3)
General comment
apps/api/src/app/agents/channels/whatsapp/send-whatsapp-test-template/send-whatsapp-test-template.usecase.ts, line 15-16 (link)The updated API contract says Embedded Signup integrations should use
sample_order_confirmation, but this use case still sendshello_worldfor every WhatsApp integration. When the saved credentials are Novu-managed and point at a real business number, Meta rejectshello_worldwith code131058, 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
General comment
bodyParameters, but the current send path ignores provided body parameters. In the after harness run, input args includedbodyParameters: ["Alice", "Order 123"], yet the constructed outbound template payload had notemplate.componentsbody component andincludesBodyComponents:false. Templates requiring body placeholders would be sent without required parameters and can be rejected by Meta.apps/api/src/app/integrations/usecases/whatsapp/whatsapp-graph-api.utils.tsdefinesSendTemplateArgswithout abodyParametersfield andsendWhatsAppTemplatebuilds a fixed payload at lines 239-250 containing onlytemplate.nameandtemplate.language, with no mapping from body parameters to Metatemplate.components[].parameters.bodyParameters?: string[](or the agreed DTO shape) toSendTemplateArgsand, when non-empty, includetemplate.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.Reviews (4): Last reviewed commit: "Merge branch 'next' into cursor/whatsapp..." | Re-trigger Greptile