whatsapp_stack.py
1 """CDK Stack for WhatsApp + Instagram integration via API Gateway + Meta Cloud API. 2 3 Reads AgentCore Runtime ARN from SSM Parameter Store (deployed by 00-agent-agentcore). 4 Supports dual-channel: WhatsApp Business and Instagram Direct Messages. 5 """ 6 7 from aws_cdk import ( 8 Stack, 9 CfnOutput, 10 RemovalPolicy, 11 aws_s3 as s3, 12 aws_iam as iam, 13 aws_secretsmanager as secretsmanager, 14 aws_ssm as ssm, 15 SecretValue, 16 ) 17 from constructs import Construct 18 19 from get_param import get_string_param 20 from databases.databases import MessageDatabase, UserIdentityDatabase 21 from layers.project_layers import ProjectLayers 22 from lambdas.project_lambdas import ProjectLambdas 23 from apis.webhooks import WebhookApi 24 25 # Read AgentCore config from SSM (set by 00-agent-agentcore stack) 26 AGENT_RUNTIME_ARN = get_string_param("/agentcore/agent_runtime_arn") 27 RUNTIME_ROLE_ARN = get_string_param("/agentcore/runtime_role_arn") 28 29 30 class WhatsAppApiGatewayStack(Stack): 31 """WhatsApp + Instagram via Meta Cloud API -> API Gateway -> Lambda pipeline -> AgentCore.""" 32 33 def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: 34 super().__init__(scope, construct_id, **kwargs) 35 36 # --- Secrets Manager for WhatsApp credentials --- 37 secrets = secretsmanager.Secret( 38 self, 39 "WhatsAppSecrets", 40 secret_object_value={ 41 "WHATS_VERIFICATION_TOKEN": SecretValue.unsafe_plain_text( 42 "CHANGE_ME_VERIFICATION_TOKEN" 43 ), 44 "WHATS_TOKEN": SecretValue.unsafe_plain_text( 45 "CHANGE_ME_TOKEN" 46 ), 47 "DISPLAY_PHONE_NUMBER": SecretValue.unsafe_plain_text( 48 "CHANGE_ME_PHONE_NUMBER" 49 ), 50 }, 51 ) 52 53 # --- Secrets Manager for Instagram credentials --- 54 ig_secrets = secretsmanager.Secret( 55 self, 56 "InstagramSecrets", 57 secret_object_value={ 58 "IG_TOKEN": SecretValue.unsafe_plain_text( 59 "CHANGE_ME_IG_TOKEN" 60 ), 61 "IG_ACCOUNT_ID": SecretValue.unsafe_plain_text( 62 "CHANGE_ME_IG_ACCOUNT_ID" 63 ), 64 "IG_VERIFICATION_TOKEN": SecretValue.unsafe_plain_text( 65 "CHANGE_ME_IG_VERIFICATION_TOKEN" 66 ), 67 }, 68 ) 69 70 # --- S3 Bucket for media and transcriptions --- 71 bucket = s3.Bucket( 72 self, 73 "MediaBucket", 74 versioned=True, 75 removal_policy=RemovalPolicy.DESTROY, 76 auto_delete_objects=True, 77 block_public_access=s3.BlockPublicAccess.BLOCK_ALL, 78 enforce_ssl=True, 79 ) 80 81 # --- DynamoDB --- 82 db = MessageDatabase(self, "Database") 83 users_db = UserIdentityDatabase(self, "UsersDatabase") 84 85 # --- Grant AgentCore runtime role access to media bucket + users table --- 86 agentcore_role = iam.Role.from_role_arn( 87 self, "AgentCoreRuntimeRole", RUNTIME_ROLE_ARN 88 ) 89 bucket.grant_read(agentcore_role) 90 users_db.table.grant_read_write_data(agentcore_role) 91 92 # --- Lambda Layers --- 93 layers = ProjectLayers(self, "Layers") 94 95 # --- Lambda Functions --- 96 lambdas = ProjectLambdas( 97 self, 98 "Lambdas", 99 table=db.table, 100 users_table=users_db.table, 101 bucket=bucket, 102 common_layer=layers.common_layer, 103 secret_arn=secrets.secret_arn, 104 ig_secret_arn=ig_secrets.secret_arn, 105 agent_runtime_arn=AGENT_RUNTIME_ARN, 106 ) 107 108 # --- API Gateway --- 109 api = WebhookApi(self, "API", whatsapp_in_fn=lambdas.webhook_receiver) 110 111 # --- Export unified_users table name to SSM for AgentCore tool --- 112 ssm.StringParameter( 113 self, 114 "UnifiedUsersTableParam", 115 parameter_name="/agentcore/unified_users_table_name", 116 string_value=users_db.table.table_name, 117 ) 118 119 # --- Outputs --- 120 CfnOutput(self, "AgentRuntimeArn", value=AGENT_RUNTIME_ARN) 121 CfnOutput(self, "MessagesTableName", value=db.table.table_name) 122 CfnOutput(self, "S3BucketName", value=bucket.bucket_name) 123 CfnOutput(self, "UnifiedUsersTableName", value=users_db.table.table_name) 124 CfnOutput(self, "SecretArn", value=secrets.secret_arn) 125 CfnOutput(self, "IGSecretArn", value=ig_secrets.secret_arn) 126 CfnOutput(self, "WebhookReceiverName", value=lambdas.webhook_receiver.function_name) 127 CfnOutput(self, "ProcessorName", value=lambdas.message_processor.function_name)