-
Notifications
You must be signed in to change notification settings - Fork 4.3k
feat(dashboard,api-service): WhatsApp Embedded Signup with inbound connection test fixes NV-8062 #11591
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
feat(dashboard,api-service): WhatsApp Embedded Signup with inbound connection test fixes NV-8062 #11591
Changes from 7 commits
6411b15
2b84db4
fa72af6
5d2ec6f
3762249
1f8dc20
7d56edf
117f329
bde34fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; | ||
| import { IsNotEmpty, IsString } from 'class-validator'; | ||
|
|
||
| import type { ConfigureWhatsAppWebhookFailure } from '../../agents/channels/whatsapp/configure-whatsapp-webhook/configure-whatsapp-webhook.usecase'; | ||
|
|
||
| export class WhatsAppEmbeddedSignupRequestDto { | ||
| @ApiProperty({ | ||
| type: String, | ||
| description: 'Authorization code returned by Meta Embedded Signup via the Facebook JS SDK', | ||
| }) | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| code: string; | ||
|
|
||
| @ApiProperty({ type: String, description: 'WhatsApp Business Account ID from the WA_EMBEDDED_SIGNUP session event' }) | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| wabaId: string; | ||
|
|
||
| @ApiProperty({ type: String, description: 'Phone number ID from the WA_EMBEDDED_SIGNUP session event' }) | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| phoneNumberId: string; | ||
|
|
||
| @ApiProperty({ type: String, description: 'Identifier of the WhatsApp integration to update' }) | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| integrationIdentifier: string; | ||
|
|
||
| @ApiProperty({ type: String, description: 'Agent identifier used to configure the webhook callback URL' }) | ||
| @IsString() | ||
| @IsNotEmpty() | ||
| agentIdentifier: string; | ||
| } | ||
|
|
||
| export type WhatsAppEmbeddedSignupFailure = { | ||
| code: | ||
| | 'feature_disabled' | ||
| | 'missing_platform_config' | ||
| | 'token_exchange_failed' | ||
| | 'meta_validation_failed' | ||
| | 'integration_not_found' | ||
| | 'phone_registration_failed' | ||
| | 'webhook_configuration_failed' | ||
| | 'unknown'; | ||
| message: string; | ||
| }; | ||
|
|
||
| export class WhatsAppEmbeddedSignupFailureDto { | ||
| @ApiProperty({ type: String }) | ||
| code: WhatsAppEmbeddedSignupFailure['code']; | ||
|
|
||
| @ApiProperty({ type: String }) | ||
| message: string; | ||
| } | ||
|
|
||
| export class WhatsAppEmbeddedSignupResponseDto { | ||
| @ApiProperty({ type: Boolean }) | ||
| success: boolean; | ||
|
|
||
| @ApiPropertyOptional({ type: String }) | ||
| integrationId?: string; | ||
|
|
||
| @ApiPropertyOptional({ type: String }) | ||
| integrationIdentifier?: string; | ||
|
|
||
| @ApiPropertyOptional({ type: String }) | ||
| callbackUrl?: string; | ||
|
|
||
| @ApiPropertyOptional({ type: String }) | ||
| wabaId?: string; | ||
|
|
||
| @ApiPropertyOptional({ | ||
| type: String, | ||
| description: 'Human-readable WhatsApp business phone number saved on the integration for onboarding deep links', | ||
| }) | ||
| displayPhoneNumber?: string; | ||
|
|
||
| @ApiPropertyOptional({ | ||
| type: String, | ||
| description: 'Present when phone registration failed but credentials were saved', | ||
| }) | ||
| phoneRegistrationWarning?: string; | ||
|
|
||
| @ApiPropertyOptional({ type: WhatsAppEmbeddedSignupFailureDto }) | ||
| error?: WhatsAppEmbeddedSignupFailure; | ||
|
|
||
| @ApiPropertyOptional({ | ||
| type: Object, | ||
| description: 'Populated when webhook auto-configure fails after credentials save', | ||
| }) | ||
| webhookReason?: ConfigureWhatsAppWebhookFailure; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,28 @@ | ||||||||||||||||||||||||||||||||||||||||||
| import { randomUUID } from 'node:crypto'; | ||||||||||||||||||||||||||||||||||||||||||
| import { ChatProviderIdEnum, type ICredentials } from '@novu/shared'; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export function resolveWhatsAppAppSecret(credentials: ICredentials): string | undefined { | ||||||||||||||||||||||||||||||||||||||||||
| if (credentials.isNovuManaged === true) { | ||||||||||||||||||||||||||||||||||||||||||
| const platformSecret = process.env.NOVU_WHATSAPP_APP_SECRET?.trim(); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return platformSecret || undefined; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const storedSecret = typeof credentials.secretKey === 'string' ? credentials.secretKey.trim() : ''; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return storedSecret || undefined; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+16
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preserve manual-credentials app ID fallback in
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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * For WhatsApp Business agent integrations Novu manages the webhook Verify | ||||||||||||||||||||||||||||||||||||||||||
| * Token automatically: it's just a shared secret echoed back to Meta during | ||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Agentic Security Review 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.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cursoragent review this comment
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||




There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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_IDandNOVU_WHATSAPP_APP_SECRET, which are required for the embedded-signup backend path. This can lead to silent misconfiguration in self-hosted setups.Suggested diff
🤖 Prompt for AI Agents