Integrating RedwoodJS with WhatsApp via AWS SNS
A developer guide on setting up a RedwoodJS application to receive and send WhatsApp messages using AWS SNS and End User Messaging Social.
Code examples21 min read
Frequently asked questions
Integrate WhatsApp with RedwoodJS using AWS SNS and AWS End User Messaging Social. This involves setting up a RedwoodJS API function as a webhook endpoint to receive incoming WhatsApp messages and send replies via the AWS SDK.
AWS End User Messaging Social is an AWS service that connects your WhatsApp Business Account (WABA) to your AWS account, enabling seamless integration with other AWS services like SNS for message handling.
AWS SNS acts as a pub/sub messaging service, allowing AWS End User Messaging Social to publish incoming WhatsApp messages to an SNS topic, which your RedwoodJS application can subscribe to for real-time message processing.
Use pre-approved WhatsApp Message Templates when replying to users outside the 24-hour free-form message window. Within 24 hours of a user's last message, you can send free-form text replies.
Yes, you can store WhatsApp message details in a database using Prisma, RedwoodJS's default ORM. Define a schema in schema.prisma and use the Prisma client in your API function to save incoming and outgoing message data.
Create a RedwoodJS API function (e.g., whatsappWebhook) and deploy it to a public HTTPS URL. Subscribe this URL to your AWS SNS topic, which receives messages from AWS End User Messaging Social linked to your WhatsApp Business Account.
WhatsApp allows free-form text replies only within 24 hours of a user's last message. Outside this window, use pre-approved Message Templates, which have a different structure within the AWS Social Post Messaging API.
Within your RedwoodJS webhook function, validate that the TopicArn in the SubscriptionConfirmation message matches your expected ARN, then use the AWS SDK's ConfirmSubscriptionCommand to programmatically confirm the subscription.
Zod is used for validating the incoming WhatsApp payload structure within the RedwoodJS function. It ensures data integrity by checking against a predefined schema, protecting your app from unexpected data formats.
Use the AWS SDK for JavaScript v3, specifically the SocialPostMessagingClient (or similar) and SendWhatsappMessageCommand with the correct parameters to send replies from your RedwoodJS API function to users' WhatsApp numbers.
Your RedwoodJS function needs AWS credentials to interact with AWS services (SNS, End User Messaging Social, etc.). Securely manage these using environment variables, IAM roles (recommended for production), or secrets management tools.
SNS signature verification validates that incoming webhook requests originate from AWS SNS, ensuring security. Use the aws-sns-verify library in your RedwoodJS function to verify the signature before processing any message data.
A DLQ (SQS queue) captures messages that SNS fails to deliver to your RedwoodJS webhook after multiple retries. This enables inspecting and potentially reprocessing failed messages, preventing data loss.
Implement try...catch blocks and logging for robust error handling. Return appropriate HTTP status codes (e.g., 403 for invalid signature, 400 for bad payload, 500 for processing errors or failed subscription confirmation to allow retries) to manage SNS retries and DLQ routing.
One API. Every channel.
Contact Sales
This guide provides a step-by-step walkthrough for building a production-ready integration between a RedwoodJS application and WhatsApp, leveraging AWS Simple Notification Service (SNS) and AWS End User Messaging Social for seamless message handling.
Project Overview and Goals
This guide details how to build a system where your RedwoodJS application can receive incoming WhatsApp messages sent to your business number and send replies back to users.
Problem Solved: Businesses need effective ways to communicate with customers on preferred platforms like WhatsApp. This integration enables automated responses, custom workflows, and data logging triggered by customer messages directly within your RedwoodJS application.
Technologies Used:
System Architecture:
Expected Outcome: A RedwoodJS application capable of:
Prerequisites:
npm install -g yarn).aws configure).1. Setting up the RedwoodJS Project
First, create a new RedwoodJS project if you don't have one already.
Create RedwoodJS App: Open your terminal and run:
yarn create redwood-app ./redwood-whatsapp-sns cd redwood-whatsapp-snsChoose your preferred options (TypeScript recommended).
Environment Variables: RedwoodJS uses a
.envfile for environment variables. Create one in the project root:touch .envAdd the following placeholders. We will populate these later.
Important: Add
.envto your.gitignorefile to avoid committing secrets. For production deployments, use environment variables provided by your hosting platform or secrets management tools instead of hardcoding AWS keys. IAM roles are the recommended approach for AWS deployments.Install AWS SDK: Install the necessary AWS SDK v3 clients for SNS and Social Messaging. Note: The package name
@aws-sdk/client-social-post-messagingis used here based on available information. Verify this is the correct and current package name in the official AWS SDK v3 documentation, as it might be part of Pinpoint, Chime SDK Messaging, or another service.Install the library for SNS signature verification:
Install Zod for validation:
Install cuid if saving outgoing messages with temporary IDs (Section 6):
2. Implementing Core Functionality: The API Function
We'll create a RedwoodJS API function to act as the webhook endpoint for SNS notifications.
Generate API Function: Use the RedwoodJS CLI to generate a new function:
yarn rw g function whatsappWebhook --tsThis creates
api/src/functions/whatsappWebhook.tsand a test file.Implement Function Logic (Initial Version - Receiving & Parsing): Replace the contents of
api/src/functions/whatsappWebhook.tswith the following code. This version includes SNS verification and parsing, preparing for reply logic.// api/src/functions/whatsappWebhook.ts import type { APIGatewayEvent, Context } from 'aws-lambda' import { logger } from 'src/lib/logger' import { Verifier } from 'aws-sns-verify' // For SNS Signature Verification import { SNSClient, ConfirmSubscriptionCommand } from '@aws-sdk/client-sns' import { z } from 'zod' // Import Prisma client if you plan to use the database // import { db } from 'src/lib/db' // Import the reply function (to be implemented in Section 4) // import { sendWhatsAppReply } from './whatsappWebhook.lib' // Example: move reply logic to separate file // Initialize AWS Clients (outside handler for potential reuse) const awsRegion = process.env.AWS_REGION || 'us-east-1' const snsClient = new SNSClient({ region: awsRegion }) // Initialize SNS Verifier (consider caching certificates in production) const verifier = new Verifier() // Zod Schema for WhatsApp Payload (Example - Verify against actual payload) // See Section 3 for details and notes on verification const WhatsappMessageSchema = z.object({ object: z.string(), entry: z.array(z.object({ changes: z.array(z.object({ value: z.object({ messaging_product: z.literal('whatsapp'), metadata: z.object({ display_phone_number: z.string(), phone_number_id: z.string(), }), contacts: z.array(z.object({ profile: z.object({ name: z.string() }), wa_id: z.string() })).optional(), messages: z.array(z.object({ from: z.string(), id: z.string(), timestamp: z.string(), type: z.string(), // 'text', 'image', etc. text: z.object({ body: z.string() }).optional(), // Add other message types (image, audio, document, location) as needed })), }), field: z.literal('messages'), })), })), }); /** * The handler function is executed for every HTTP request that reaches the endpoint. * RedwoodJS automatically routes requests to `/api/whatsappWebhook` to this function. * When subscribed to SNS, AWS sends POST requests with SNS notification payloads. */ export const handler = async (event: APIGatewayEvent, _context: Context) => { logger.info('Received request on whatsappWebhook function') // --- 1. Validate SNS Signature (Mandatory for Production) --- let snsMessage: any try { // aws-sns-verify expects the raw payload parsed as JSON const rawPayload = JSON.parse(event.body || '{}') await verifier.verify(rawPayload) // Throws error if invalid snsMessage = rawPayload // Use the verified payload logger.info({ type: snsMessage.Type }, 'SNS Signature Verified Successfully') } catch (error) { logger.error({ error, body: event.body }, 'SNS Signature Verification Failed') return { statusCode: 403, body: 'Forbidden: Invalid SNS Signature' } } // --- 2. Handle SNS Subscription Confirmation --- if (snsMessage.Type === 'SubscriptionConfirmation') { // **VERY IMPORTANT**: Validate snsMessage.TopicArn === process.env.INCOMING_SNS_TOPIC_ARN // before confirming automatically to prevent misuse. if (snsMessage.TopicArn !== process.env.INCOMING_SNS_TOPIC_ARN) { logger.error('Attempted subscription confirmation for wrong topic ARN:', snsMessage.TopicArn); return { statusCode: 400, body: 'Bad Request: Invalid Topic ARN' }; } logger.info(`Attempting to confirm SNS subscription programmatically for token: ${snsMessage.Token}`); try { const command = new ConfirmSubscriptionCommand({ TopicArn: snsMessage.TopicArn, // Use ARN from message after validation Token: snsMessage.Token, }); await snsClient.send(command); logger.info('SNS Subscription Confirmed programmatically'); return { statusCode: 200, body: 'Subscription Confirmed' }; } catch (confirmError) { logger.error({ confirmError }, 'Failed to confirm SNS subscription'); // Let SNS retry if confirmation fails temporarily return { statusCode: 500, body: 'Subscription confirmation failed' }; } } // --- 3. Handle Actual Notification (Incoming WhatsApp Message) --- if (snsMessage.Type === 'Notification') { try { // Parse the inner message which contains the WhatsApp payload const rawWhatsappPayload = JSON.parse(snsMessage.Message) // Validate the WhatsApp payload structure const validationResult = WhatsappMessageSchema.safeParse(rawWhatsappPayload); if (!validationResult.success) { logger.error({ errors: validationResult.error.issues, payload: rawWhatsappPayload }, 'WhatsApp payload validation failed'); // Return 400 Bad Request as retrying won't fix a structural issue return { statusCode: 400, body: 'Bad Request: Invalid WhatsApp payload structure' }; } const whatsappPayload = validationResult.data; // Use validated data logger.info( { messageId: whatsappPayload?.entry?.[0]?.changes?.[0]?.value?.messages?.[0]?.id }, 'Processing validated incoming WhatsApp message notification' ) // Extract relevant information const messageEntry = whatsappPayload.entry[0]?.changes[0]?.value; const messageDetails = messageEntry?.messages?.[0]; const senderWaId = messageDetails?.from; // WhatsApp ID (phone number) const messageText = messageDetails?.text?.body; const senderProfileName = messageEntry?.contacts?.[0]?.profile?.name; const messageTimestamp = messageDetails?.timestamp; const messageId = messageDetails?.id; // WhatsApp message ID if (!senderWaId || !messageId) { logger.warn({ payload: whatsappPayload }, 'Essential message details missing from payload') // Return 400 Bad Request return { statusCode: 400, body: 'Bad Request: Incomplete message data' } } logger.info( `Message ID ${messageId} from ${senderProfileName || 'Unknown'} (${senderWaId}): ""${messageText || '[Non-text message]'}""` ) // --- TODO: Add your business logic here --- // - Save message to DB using Prisma (See Section 6) // - Determine response based on messageText // - Trigger other workflows // --- TODO: Call function to send reply (Implement in Section 4) --- // Example call: // const replyText = `Received: ""${messageText}""`; // await sendWhatsAppReply(senderWaId, replyText); // logger.info(`Reply attempt initiated for message ID ${messageId}`); // Remember the 24-hour rule for free-form replies (See Section 4 & 8) // Acknowledge receipt to SNS return { statusCode: 200, body: 'Notification received successfully' } } catch (error) { // Catch errors during inner message parsing or business logic (but not reply sending yet) logger.error({ error, snsMessage }, 'Error processing SNS Notification message') // Return 500 to allow SNS retries for potentially transient processing issues // If the error is definitely permanent (e.g., validation already handled), consider 400. return { statusCode: 500, body: 'Internal Server Error processing notification' } } } // --- 4. Handle other SNS message types if necessary --- logger.warn({ snsMessageType: snsMessage.Type }, 'Received unhandled SNS message type') return { statusCode: 200, body: 'Message received but not processed' } // Acknowledge other types }Explanation:
handlerreceives the HTTP request from SNS.aws-sns-verifyto ensure the request is authentic before processing. This is crucial for security.Messagefield (containing the WhatsApp payload).WhatsappMessageSchema(Zod).200 OKfor successful processing or appropriate error codes (403for bad signature,400for invalid payload,500for processing errors or failed subscription confirmation to allow retries).3. Building the API Layer
In RedwoodJS, the API function
whatsappWebhookis the API layer for this integration point.Endpoint: RedwoodJS exposes this function at
/api/whatsappWebhook. This is the URL you provide to AWS SNS.Authentication/Authorization:
Request Validation:
WhatsappMessageSchema) validates the structure of the inner WhatsApp payload (snsMessage.Message). Important: This schema is an example based on common structures. You must verify and potentially adjust it based on the actual payloads you receive from Meta/AWS via SNS, as these can evolve. Test with real messages.API Documentation (SNS Input): The function expects POST requests with a JSON body matching the AWS SNS notification format. The critical field is
Message, which contains a stringified JSON payload from AWS End User Messaging Social, mirroring Meta's Webhook format.{ "Type": "Notification", "MessageId": "abcd-1234-...", "TopicArn": "arn:aws:sns:us-east-1:111122223333:YourTopicName", "Subject": null, "Message": "{\"object\":\"whatsapp_business_account\",\"entry\":[{\"id\":\"WABA_ID\",\"changes\":[{\"value\":{\"messaging_product\":\"whatsapp\",\"metadata\":{\"display_phone_number\":\"+1555...\",\"phone_number_id\":\"YOUR_WABA_PHONE_NUMBER_ID\"},\"contacts\":[{\"profile\":{\"name\":\"Test User\"},\"wa_id\":\"16505551234\"}],\"messages\":[{\"from\":\"16505551234\",\"id\":\"wamid.HB...\",\"timestamp\":\"1679...\",\"text\":{\"body\":\"Hello Redwood\"},\"type\":\"text\"}]},\"field\":\"messages\"}]}]}", "Timestamp": "2024-04-20T10:30:00.000Z", "SignatureVersion": "1", "Signature": "...", "SigningCertURL": "...", "UnsubscribeURL": "..." }MessagePayload (Parsed WhatsApp Event - for clarity): This is what theJSON.parse(snsMessage.Message)call produces (structure needs verification):{ "object": "whatsapp_business_account", "entry": [ { "id": "WABA_ID", "changes": [ { "value": { "messaging_product": "whatsapp", "metadata": { "display_phone_number": "+1555...", "phone_number_id": "YOUR_WABA_PHONE_NUMBER_ID" }, "contacts": [ { "profile": { "name": "Test User" }, "wa_id": "16505551234" } ], "messages": [ { "from": "16505551234", "id": "wamid.HB...", "timestamp": "1679...", "text": { "body": "Hello Redwood" }, "type": "text" } ] }, "field": "messages" } ] } ] }Testing (Simulating SNS): Use
curlor Postman to send a POST request to your local development server's webhook endpoint (http://localhost:8911/api/whatsappWebhookby default). You'll need to generate a valid signature or temporarily disable verification for local testing. Tools exist to help craft signed SNS messages if needed. Basiccurlwithout a valid signature will be rejected if verification is active.4. Integrating with AWS Services (SNS, End User Messaging, Sending Replies)
This section covers setting up AWS infrastructure and enabling replies.
Create SNS Topic (AWS Console or CLI):
Standard, Name:WhatsAppIncomingMessages(or similar). Note the Topic ARN. Add it to your.env(INCOMING_SNS_TOPIC_ARN).aws sns create-topic --name WhatsAppIncomingMessages --region YOUR_REGION(Copy theTopicArn).Link WABA to AWS End User Messaging Social:
INCOMING_SNS_TOPIC_ARN)..envfile (WABA_PHONE_NUMBER_ID).Subscribe RedwoodJS Function to SNS Topic:
https://your-app.com/api/whatsappWebhook.HTTPS, Endpoint:https://your-app.com/api/whatsappWebhook, Disable ""Enable raw message delivery"". Create subscription.aws sns subscribe --topic-arn YOUR_SNS_TOPIC_ARN --protocol https --endpoint https://your-app.com/api/whatsappWebhook --region YOUR_REGIONSubscriptionConfirmationmessage. Your deployed function's logic (from Section 2) should handle this programmatically (validating the ARN and callingConfirmSubscriptionCommand). Monitor logs to ensure confirmation succeeds. The subscription status in AWS changes from ""Pending confirmation"" to ""Confirmed"".Implement Sending Replies: Update the
whatsappWebhook.tsfunction (or create a helper file likewhatsappWebhook.lib.ts) to include the reply logic.// api/src/functions/whatsappWebhook.ts OR whatsappWebhook.lib.ts import { logger } from 'src/lib/logger' import { SocialPostMessagingClient, // Verify this is the correct client name from the SDK package SendWhatsappMessageCommand, SendWhatsappMessageCommandInput, } from '@aws-sdk/client-social-post-messaging' // Verify this package name (@aws-sdk/...) import { z } from 'zod' // If used for error handling here // Initialize AWS SDK Client for sending messages const awsRegion = process.env.AWS_REGION || 'us-east-1' // Verify the correct client to use based on the package name verification const socialMessagingClient = new SocialPostMessagingClient({ region: awsRegion }) /** * Sends a simple text reply via AWS Social Messaging API. * * VERY IMPORTANT: WhatsApp enforces a 24-hour window for free-form replies * after the last incoming message from the user. This function, sending * free-form text, will ONLY work within that window. Outside the 24-hour * window, you MUST use pre-approved WhatsApp Message Templates. Sending * templates requires a different structure in the SendWhatsappMessageCommandInput. * * @param recipientWaId - The user's WhatsApp ID (e.g., ""16505551234"") * @param messageBody - The text content of the reply message */ export async function sendWhatsAppReply(recipientWaId: string, messageBody: string): Promise<void> { const wabaPhoneNumberId = process.env.WABA_PHONE_NUMBER_ID if (!wabaPhoneNumberId) { logger.error('WABA_PHONE_NUMBER_ID environment variable is not set.') // Throw error to be caught by the main handler throw new Error('Missing WABA Phone Number ID configuration.') } // --- CRITICAL: VERIFY PARAMS STRUCTURE --- // The structure of `SendWhatsappMessageCommandInput` (`params`) below is a // PLAUSIBLE EXAMPLE based on common AWS SDK patterns and CLI examples. // You MUST verify the exact required fields and structure against the // official AWS SDK v3 documentation for the specific SDK client/package // you are using (e.g., @aws-sdk/client-social-post-messaging or alternatives // like Pinpoint/Chime SDK if applicable). // The correct structure is ESSENTIAL for this function to work. const params: SendWhatsappMessageCommandInput = { // Option 1: Guess based on potential SDK structure OriginationIdentity: wabaPhoneNumberId, // Or OriginationPhoneNumberId? DestinationIdentity: recipientWaId, // Or ToAddress? Content: { Text: messageBody, // For Template Messages, structure would differ, e.g.: // Template: { Name: 'template_name', LanguageCode: 'en_US', Parameters: [...] } }, // Configuration: { WhatsApp: { /* ... maybe needed ... */ } }, // Option 2: Closer to CLI structure (less likely for SDK v3 but possible) // Message: JSON.stringify({ // messaging_product: 'whatsapp', // to: recipientWaId, // type: 'text', // text: { preview_url: false, body: messageBody }, // }), // OriginationPhoneNumberId: wabaPhoneNumberId, } // --- END CRITICAL VERIFICATION NOTE --- try { logger.info({ recipientWaId, wabaPhoneNumberId, bodyLength: messageBody.length }, 'Attempting to send WhatsApp reply via AWS SDK') // Ensure 'params' matches the verified structure from AWS SDK docs const command = new SendWhatsappMessageCommand(params) const result = await socialMessagingClient.send(command) // Log the message ID returned by AWS if available (field name might vary) logger.info({ awsMessageId: result.MessageId /* or similar field like result.messageMetadata?.messageId */ }, 'Successfully sent WhatsApp reply via AWS SDK') } catch (error) { logger.error({ error, recipientWaId, wabaPhoneNumberId }, 'Failed to send WhatsApp reply via AWS SDK') // Re-throw the error to be caught by the main handler, allowing SNS retries // if the error seems transient (e.g., network issue, throttling). throw error // Let the caller handle retry logic/response codes } } // --- In whatsappWebhook.ts (handler function) --- // Make sure to import and call sendWhatsAppReply: /* import { sendWhatsAppReply } from './whatsappWebhook.lib' // Or wherever defined // ... inside the 'Notification' block after processing ... try { // ... existing logic ... // --- Business Logic & Reply --- let replyText = `Hi ${senderProfileName || 'there'}, we received: ""${messageText}"".`; // Add logic to determine replyText... // Call the reply function // IMPORTANT: This free-form reply only works within 24hrs of user's message! await sendWhatsAppReply(senderWaId, replyText); logger.info(`Reply sent for message ID ${messageId}`); // Optional: Save outgoing message to DB (See Section 6) return { statusCode: 200, body: 'Notification received and reply sent' }; } catch (error) { logger.error({ error }, 'Failed processing notification or sending reply'); if (error instanceof z.ZodError) { // Example specific error handling return { statusCode: 400, body: 'Bad Request: Invalid payload' }; } // Let SNS retry for potentially transient errors (like SDK call failures) return { statusCode: 500, body: 'Internal Server Error during processing or reply' }; } */Key Changes & Considerations:
sendWhatsAppReplyusing the client.SendWhatsappMessageCommandInputstructure (params) must be verified against official AWS SDK documentation. The provided code is a placeholder guess.sendWhatsAppReplyinto the main handler'sNotificationblock (shown commented out for integration).5. Implementing Error Handling, Logging, and Retry Mechanisms
try...catchblocks extensively.403 Forbidden. No retry needed.400 Bad Request. The message structure is wrong; retrying won't help.500 Internal Server Error. Fix config and potentially use DLQ for failed messages.sendWhatsAppReply): Catch errors. Log details. Return500 Internal Server Errorto signal to SNS that it should retry (could be throttling, temporary network issue).500 Internal Server Errorfor potential retries.logger(import { logger } from 'src/lib/logger').logger.info({ key: value }, 'Message').LOG_LEVELper environment.5xxerrors or timeouts. Customize the retry policy (attempts, backoff) in the SNS subscription settings if needed.setTimeout) to ensure correct error code responses and DLQ behavior.6. Creating a Database Schema and Data Layer (Optional)
Use Prisma to store message history or related data.
Define Schema: Edit
api/db/schema.prisma.Apply Schema Changes: Run Prisma Migrate to update your database schema and generate the Prisma Client.
Use Prisma Client in API Function: Modify
api/src/functions/whatsappWebhook.tsto save incoming messages.// api/src/functions/whatsappWebhook.ts // ... other imports ... import { db } from 'src/lib/db' // Import Prisma client import cuid from 'cuid' // If needed for outgoing message temp ID // ... inside the 'Notification' block ... if (snsMessage.Type === 'Notification') { try { // ... existing parsing and validation ... if (!senderWaId || !messageId) { // ... handle missing data ... return { statusCode: 400, body: 'Bad Request: Incomplete message data' } } logger.info( `Message ID ${messageId} from ${senderProfileName || 'Unknown'} (${senderWaId}): ""${messageText || '[Non-text message]'}""` ) // --- Save Incoming Message to DB --- try { await db.whatsappMessage.create({ data: { whatsappId: messageId, // Use Meta's message ID as unique identifier senderWaId: senderWaId, recipientWaId: messageEntry.metadata.phone_number_id, // Your WABA ID from payload body: messageText, // Null if not a text message type: messageDetails.type, direction: 'incoming', timestamp: new Date(parseInt(messageTimestamp) * 1000), // Convert Unix timestamp string to Date // status: null, // Status usually applies to outgoing }, }) logger.info({ whatsappId: messageId }, 'Incoming message saved to database') } catch (dbError) { // Log error, but decide if it should prevent acknowledging SNS // If DB save is critical, return 500 to retry. If not, maybe just log and continue. logger.error({ error: dbError, whatsappId: messageId }, 'Failed to save incoming message to database') // Example: Allow processing to continue but log failure // return { statusCode: 500, body: 'Internal Server Error saving message' }; // Uncomment to force retry on DB error } // --- Business Logic & Reply --- let replyText = `Hi ${senderProfileName || 'there'}, we received: ""${messageText}"".`; // Add logic to determine replyText... // --- Send Reply (Call function from Section 4) --- try { // IMPORTANT: Free-form reply only works within 24hrs! await sendWhatsAppReply(senderWaId, replyText); logger.info(`Reply sent for message ID ${messageId}`); // --- Optional: Save Outgoing Message Placeholder --- // You might want to save a record for the outgoing message. // AWS SDK response might contain an ID, but it's async. // You could save a placeholder and update status later via separate webhooks if needed. // const outgoingTempId = cuid(); // Generate a temporary ID if needed // try { // await db.whatsappMessage.create({ // data: { // id: outgoingTempId, // Use temp ID or let Prisma generate one // whatsappId: `temp_${outgoingTempId}`, // Placeholder until real ID known (if ever) // senderWaId: messageEntry.metadata.phone_number_id, // Your WABA ID // recipientWaId: senderWaId, // User's WA ID // body: replyText, // type: 'text', // direction: 'outgoing', // timestamp: new Date(), // Time reply was initiated // status: 'sent', // Initial status assumption // }, // }); // logger.info({ tempId: outgoingTempId }, 'Outgoing message placeholder saved'); // } catch (dbError) { // logger.error({ error: dbError }, 'Failed to save outgoing message placeholder'); // } } catch (replyError) { logger.error({ error: replyError, whatsappId: messageId }, 'Failed to send WhatsApp reply'); // Let SNS retry for potentially transient SDK errors return { statusCode: 500, body: 'Internal Server Error sending reply' }; } // Acknowledge receipt to SNS if everything up to here is successful // (or if DB errors are logged but not fatal) return { statusCode: 200, body: 'Notification processed successfully' } } catch (error) { // Catch errors during parsing, validation, or unhandled DB/reply errors logger.error({ error, snsMessage }, 'Error processing SNS Notification message') if (error instanceof z.ZodError) { return { statusCode: 400, body: 'Bad Request: Invalid WhatsApp payload structure' }; } // Return 500 for other processing errors to allow SNS retries return { statusCode: 500, body: 'Internal Server Error processing notification' } } } // ... rest of handler ...Key Changes:
dbfromsrc/lib/db.try...catchblock arounddb.whatsappMessage.createto save the incoming message details.Dateobject.Remember to configure your
DATABASE_URLenvironment variable in.envfor Prisma to connect to your database.