code examples
code examples
Sinch WhatsApp Integration Next.js Tutorial: Send & Receive Messages
Learn how to integrate Sinch WhatsApp Business API with Next.js. Complete step-by-step tutorial covering message sending, webhooks, authentication, and production deployment with the Conversation API.
How to Integrate Sinch WhatsApp with Next.js: Complete Developer Guide
Build robust WhatsApp messaging into your Next.js application using the Sinch Conversation API. This tutorial shows you how to send and receive WhatsApp messages, handle webhooks, manage security, and deploy a production-ready solution.
You'll create a Next.js application with API routes that interact with the Sinch Conversation API to send outbound WhatsApp messages and process inbound messages via webhooks. By the end, you'll have a functional bidirectional integration ready for testing and deployment.
What Will You Build with This Tutorial?
Objective: Implement WhatsApp messaging within your Next.js application to send outbound messages and receive inbound messages via the Sinch Conversation API.
Problem Solved: Programmatically communicate with users on WhatsApp for notifications, customer support, alerts, order updates, and marketing – directly from your web application.
Technologies & Requirements (Verified January 2025):
- Next.js 13+: React framework for full-stack web applications. This guide uses the App Router (introduced in Next.js 13, stable in Next.js 14). Use Next.js 14 or Next.js 15 for the latest features and stability. The App Router provides modern API routes with improved performance.
- Node.js 18.18.0+: JavaScript runtime environment. Use Node.js 20 LTS "Iron" (Maintenance LTS until April 2026) or Node.js 22 LTS "Jod" (Active LTS until October 2027). Node.js 18 LTS "Hydrogen" ended support in March 2025.
- Sinch Conversation API: Unified API for managing conversations across multiple channels, including WhatsApp Business API. Includes template messages, media handling, and delivery tracking.
- WhatsApp Business API: Meta's official business messaging platform. Requires an approved WhatsApp Business Account through Sinch's embedded signup. WhatsApp enforces a 24-hour messaging window for session messages. Use template messages to initiate conversations or message users outside this window.
System Architecture:
- End user's WhatsApp client communicates with the WhatsApp Business API platform
- WhatsApp platform relays messages to and from Sinch Conversation API
- Your Next.js application interacts with Sinch API to send messages (via HTTP) and receive messages/events (via webhooks)
- Your application logic triggers outbound messages and optionally stores message history in a database
Final Outcome – A Next.js Application With:
- API endpoint (
/api/send-whatsapp) to send WhatsApp messages via Sinch - API endpoint (
/api/sinch-webhook) to receive incoming message notifications from Sinch - Secure credential handling and comprehensive error handling
- Production deployment instructions and testing guidance
Prerequisites:
- Node.js 18.18.0+ (LTS version recommended) and npm or yarn installed. Verify with
node --version - Sinch account with Conversation API access. Enable postpay billing to send messages beyond initial free credits or for production volumes. Trial accounts work for initial testing within limits. Contact Sinch support for billing details.
- Configured Sinch Conversation API App (covered in this guide)
- Provisioned WhatsApp Business Sender ID via Sinch using Embedded Signup process
- Familiarity with JavaScript, React, Next.js App Router, and REST APIs
- Understanding of E.164 phone number format (international format with + prefix, e.g., +15551234567)
Technology Verification Date: All technology versions and requirements verified as of January 2025. Check official documentation for latest releases.
How Do You Set Up Your Next.js Project for Sinch WhatsApp?
Initialize your Next.js project and configure environment variables for Sinch integration.
Creating Your Next.js App with App Router
Open your terminal and run:
npx create-next-app@latest sinch-whatsapp-nextjs
# Follow the prompts (e.g., select TypeScript: No, ESLint: Yes, Tailwind CSS: No, `src/` directory: No, App Router: Yes (Recommended), Import alias: No)
cd sinch-whatsapp-nextjsHow to Configure Sinch Environment Variables Securely
Sinch requires several credentials. Store them securely in environment variables. Create a .env.local file in your project root.
Never commit .env.local to version control. Add it to .gitignore if it's not already there.
Important: Replace all YOUR_... placeholders with your actual credentials from the Sinch dashboard (explained later).
# .env.local
# Sinch Project & API Access Credentials
# Found in Sinch Dashboard -> Your Project -> API Keys
SINCH_PROJECT_ID=YOUR_SINCH_PROJECT_ID
SINCH_KEY_ID=YOUR_SINCH_ACCESS_KEY_ID
SINCH_KEY_SECRET=YOUR_SINCH_ACCESS_KEY_SECRET
# Sinch Conversation API App Credentials
# Found in Sinch Dashboard -> Conversation API -> Your App
SINCH_APP_ID=YOUR_CONVERSATION_APP_ID
# Typically 'us', 'eu', or 'br' - Match your Conversation API App region
SINCH_REGION=us
# Sinch WhatsApp Channel Credentials (Specific to your configured WhatsApp Sender)
# Found during WhatsApp Sender setup or in App Configuration
SINCH_WHATSAPP_SENDER_ID=YOUR_WHATSAPP_SENDER_ID # Often the phone number in E.164 format
# Note: This token is essential for configuring the WhatsApp channel in the Sinch App (dashboard setup).
# The API calls in this guide use Basic Authentication derived from SINCH_KEY_ID/SECRET for sending.
SINCH_WHATSAPP_BEARER_TOKEN=YOUR_WHATSAPP_SENDER_BEARER_TOKEN # Provided during sender setup
# Application URL (Needed for Webhook setup in Sinch Dashboard)
# Use your deployed URL in production. For local dev, use an ngrok URL.
NEXT_PUBLIC_APP_URL=http://localhost:3000 # Replace with deployment or ngrok URL later- Purpose: Store sensitive credentials outside your codebase for security. Next.js automatically loads
.env.localvariables intoprocess.env. - Obtaining Values: The "Get Your Sinch Credentials" section explains how to find these values in the Sinch Customer Dashboard.
NEXT_PUBLIC_APP_URL: Set this to your application's publicly accessible base URL. Sinch uses this to send webhooks. TheNEXT_PUBLIC_prefix follows Next.js convention.
Understanding the Next.js App Router Project Structure
You'll work primarily in the app/api/ directory for backend logic.
sinch-whatsapp-nextjs/
├── app/
│ ├── api/
│ │ ├── send-whatsapp/
│ │ │ └── route.js # Endpoint to send messages
│ │ └── sinch-webhook/
│ │ └── route.js # Endpoint to receive webhooks
│ ├── layout.js
│ └── page.js # Optional simple UI for testing
├── .env.local # Your secret credentials (DO NOT COMMIT)
├── .env.local.example # Example structure for others (Commit this)
├── .gitignore
├── next.config.js
├── package.json
└── README.md
- Architectural Decision: Next.js API routes keep backend logic colocated with your frontend, simplifying development and deployment. The App Router provides a modern, streamlined approach.
How Do You Send WhatsApp Messages with Sinch Conversation API?
Create the API endpoint that sends outbound WhatsApp messages via Sinch.
Understanding WhatsApp Message Types
WhatsApp enforces specific rules for message types:
| Message Type | Use Case | Requirements |
|---|---|---|
Session Messages (text_message) | Reply within 24 hours of user's last message | No pre-approval needed; allows free-form text and media |
Template Messages (template_message) | Initiate conversations or message outside 24-hour window | Must be pre-approved by WhatsApp; strict formatting with placeholders |
When to use each type:
- Use
text_messagefor ongoing conversations where the user messaged you recently (within 24 hours) - Use
template_messageto:- Start new conversations
- Send scheduled reminders
- Reach users who haven't messaged in 24+ hours
- Send notifications, alerts, or marketing (with user opt-in)
File: app/api/send-whatsapp/route.js
// app/api/send-whatsapp/route.js
import { NextResponse } from 'next/server';
// Basic Authentication Token Generation (cache in production)
function getSinchAuthToken() {
const keyId = process.env.SINCH_KEY_ID;
const keySecret = process.env.SINCH_KEY_SECRET;
if (!keyId || !keySecret) {
throw new Error("Sinch Key ID or Secret not configured in environment variables.");
}
const credentials = `${keyId}:${keySecret}`;
return `Basic ${Buffer.from(credentials).toString('base64')}`;
// Note: For production, consider Sinch OAuth for better security & rotation
// See: https://developers.sinch.com/docs/conversation/authentication/
}
export async function POST(request) {
const { to, text } = await request.json();
// --- 1. Input Validation ---
if (!to || !text) {
return NextResponse.json({ error: 'Missing "to" phone number or "text" message.' }, { status: 400 });
}
// Basic E.164 format check (adjust regex as needed for stricter validation)
if (!/^\+\d{11,15}$/.test(to)) {
return NextResponse.json({ error: 'Invalid "to" phone number format. Use E.164 (e.g., +15551234567).' }, { status: 400 });
}
const projectId = process.env.SINCH_PROJECT_ID;
const sinchAppId = process.env.SINCH_APP_ID;
const senderId = process.env.SINCH_WHATSAPP_SENDER_ID; // Used in payload, ensure it's set
const region = process.env.SINCH_REGION || 'us'; // Default to 'us' if not set
if (!projectId || !sinchAppId || !senderId) {
return NextResponse.json({ error: 'Sinch Project ID, App ID, or Sender ID not configured.' }, { status: 500 });
}
const sinchApiUrl = `https://${region}.conversation.api.sinch.com/v1/projects/${projectId}/messages:send`;
const payload = {
app_id: sinchAppId,
recipient: {
contact_id: to // Using contact_id for direct E.164 number sending
// Alternatively, use channel_identity for specific channel/number pair:
// channel_identity: { channel: 'WHATSAPP', identity: to }
},
message: {
text_message: {
text: text
}
// For Template Messages, use:
// template_message: {
// template_id: "your_template_id", // The registered template name/ID
// version: "your_template_version", // e.g., "1"
// language_code: "en_US", // Or relevant language
// parameters: { /* template parameters if any */ }
// }
},
channel_priority_order: ["WHATSAPP"] // Ensure WhatsApp is prioritized
};
try {
// --- 2. API Call to Sinch ---
const response = await fetch(sinchApiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': getSinchAuthToken() // Use Basic Auth or OAuth
},
body: JSON.stringify(payload),
});
const responseData = await response.json();
// --- 3. Response Handling ---
if (!response.ok) {
console.error('Sinch API Error:', response.status, responseData);
return NextResponse.json(
{ error: 'Failed to send message via Sinch.', details: responseData },
{ status: response.status }
);
}
console.log('Sinch Send Success:', responseData);
return NextResponse.json({ success: true, messageId: responseData.message_id });
} catch (error) {
// --- 4. Error Handling ---
console.error('Error sending WhatsApp message:', error);
return NextResponse.json({ error: 'Internal server error.' }, { status: 500 });
}
}Code Walkthrough:
- Input Validation: Validates that
to(recipient phone number in E.164 format) andtextfields exist in the request body. Includes basic E.164 format checking. - Authentication: The
getSinchAuthTokenfunction generates the Basic Authentication header using your Key ID and Secret. For production, implement Sinch's OAuth 2.0 flow for more secure, short-lived tokens. - Payload Construction: Builds the JSON payload according to the Sinch Conversation API
/messages:sendendpoint specification. Includes the target app, recipient (usingcontact_id), message content (text_message), and prioritizes theWHATSAPPchannel. Comments show how to structure atemplate_message(required outside the 24-hour window or for initiating conversations). - API Call: Uses the native
fetchAPI to send the POST request to the appropriate regional Sinch API endpoint. - Response Handling: Checks if the API call succeeded (
response.ok). Logs error details returned by Sinch on failure and sends an appropriate error response to the client. On success, logs the result and returns themessage_id. - Error Handling: A
try...catchblock handles network errors or unexpected issues during the process.
How Do You Receive WhatsApp Messages Using Sinch Webhooks?
Sinch needs a publicly accessible endpoint to send incoming messages and delivery status updates via POST requests (webhooks).
File: app/api/sinch-webhook/route.js
// app/api/sinch-webhook/route.js
import { NextResponse } from 'next/server';
export async function POST(request) {
try {
const payload = await request.json();
console.log('Received Sinch Webhook');
console.log('Payload:', JSON.stringify(payload, null, 2)); // Log the full payload for inspection
// --- 1. Identify Event Type & Process based on Payload Structure ---
// Sinch uses different payload structures for different events.
// Inspect payloads in the Sinch Dashboard or your logs to handle each relevant case.
if (payload.message_inbound_event) {
// --- 2. Handle Incoming Messages ---
const event = payload.message_inbound_event;
const message = event.message;
const contactId = event.contact_id; // Sender's phone number (usually)
const appId = event.app_id;
console.log(`---> Incoming Message from ${contactId} in App ${appId}`);
if (message.text_message) {
console.log(` Text: ${message.text_message.text}`);
// TODO: Add your logic here:
// - Store the message in a database
// - Trigger automated replies
// - Forward to a support system
} else if (message.media_message) {
console.log(` Media URL: ${message.media_message.url}`);
// TODO: Handle incoming media (images, videos, etc.)
}
// Add handling for other message types (location, contacts, etc.) as needed
} else if (payload.message_delivery_event) {
// --- 3. Handle Delivery Receipts ---
const event = payload.message_delivery_event;
const messageId = event.message_id;
const status = event.status; // e.g., DELIVERED, FAILED, READ
const reason = event.reason; // Reason for failure, if any
console.log(`---> Delivery Status for Message ${messageId}: ${status}`);
if (reason) {
console.log(` Reason: ${reason}`);
}
// TODO: Update message status in your database
} else {
// Log other event types you might receive but aren't explicitly handling yet
const eventType = Object.keys(payload)[0]; // Basic way to guess event type key
console.warn(`Received unhandled webhook event type: ${eventType || 'Unknown'}`);
}
// --- 4. Acknowledge Receipt ---
// Respond quickly to Sinch to acknowledge receipt.
// Process heavy logic asynchronously (e.g., using queues or background jobs).
return NextResponse.json({ success: true });
} catch (error) {
console.error('Error processing Sinch webhook:', error);
// Avoid sending detailed errors back in the webhook response
return NextResponse.json({ error: 'Webhook processing failed.' }, { status: 500 });
}
}
// --- Optional: GET handler for verification ---
// Some services require a GET request check during webhook setup.
// Sinch doesn't typically require this, but it's useful for health checks.
export async function GET(request) {
console.log('Received GET request on webhook endpoint.');
return NextResponse.json({ message: 'Sinch webhook endpoint is active.' });
}Code Walkthrough:
- Receive Payload: Parses the incoming JSON payload sent by Sinch.
- Event Type Identification: Checks the payload structure (e.g., presence of
message_inbound_eventormessage_delivery_eventkeys) to determine the event type. Inspect actual webhook payloads received from Sinch (viewable in the Sinch Dashboard or your logs) to confirm the exact structure and event types you need to handle. - Incoming Message Handling: For inbound messages, extracts key information like the sender (
contact_id), message content (text_message,media_message, etc.), and logs it. Integrate your application logic here – save messages, trigger replies, etc. - Delivery Receipt Handling: For delivery events, extracts the message ID, status (
DELIVERED,FAILED,READ), and potentially a failure reason. Use this for tracking message status. - Acknowledgement: Returns a
200 OKresponse (NextResponse.json({ success: true })) quickly. Sinch expects timely acknowledgement. Move time-consuming processing to asynchronous operations (e.g., job queue like BullMQ, or serverless functions) to prevent webhook timeouts. - Error Handling: Catches errors during processing and logs them, returning a generic
500error to Sinch. Avoid sending detailed internal error messages. - GET Handler (Optional): A simple GET handler for manually checking if the endpoint is reachable and deployed correctly.
Security Note: For production, implement webhook signature verification to ensure incoming requests genuinely originate from Sinch. Sinch likely provides a mechanism (e.g., using a secret key to generate an HMAC signature in request headers). Consult the official Sinch Conversation API documentation for specific details on implementing webhook signature verification and add the necessary validation logic to this webhook handler. This is a critical security step.
How to Get Your Sinch Credentials and Configure WhatsApp
Obtain the necessary credentials and configure your Sinch App. Replace YOUR_... placeholders in .env.local with the actual values you obtain here.
How to Obtain Sinch API Credentials
- Log in: Access the Sinch Customer Dashboard.
- Project ID: Navigate to "Settings" (usually bottom-left) → "Projects". Your Project ID is listed there. Copy this to
SINCH_PROJECT_IDin your.env.local. - API Keys (Key ID & Secret):
- Go to "Settings" → "API Keys".
- Click "CREATE KEY".
- Give it a descriptive name (e.g., "NextJS WhatsApp App Key").
- Important: Copy the Key ID and Key Secret immediately and store them securely in your
.env.local(SINCH_KEY_ID,SINCH_KEY_SECRET). The Key Secret is only shown once.
- Conversation API Access & App:
- Navigate to "Conversation API" in the left menu. If you haven't already, click "GET ACCESS" and agree to the terms.
- Click "CREATE APP".
- Give your app a name (e.g., "My NextJS WhatsApp App").
- Select the appropriate Region (e.g., US, EU, BR). This must match the
SINCH_REGIONin your.env.local. - Click "CREATE".
- On the app's overview page, find the App ID. Copy this to
SINCH_APP_IDin your.env.local.
- WhatsApp Sender ID & Bearer Token:
- Complete the WhatsApp Embedded Signup process within the Sinch Dashboard. Follow Sinch's guide: How do I use the WhatsApp embedded signup process? (or equivalent up-to-date documentation).
- During this process, you'll link a phone number and obtain:
- WhatsApp Sender ID: Usually the phone number in E.164 format (e.g.,
+15551234567). Copy this toSINCH_WHATSAPP_SENDER_ID. - Bearer Token (Access Token): A token specific to this WhatsApp Sender. Copy this to
SINCH_WHATSAPP_BEARER_TOKEN. This token is needed for linking the sender in the dashboard setup.
- WhatsApp Sender ID: Usually the phone number in E.164 format (e.g.,
How to Configure Your Sinch Conversation API App for WhatsApp
- Navigate to your App: In the Sinch Dashboard, go to "Conversation API" → "Your Apps" and select the app you created.
- Add WhatsApp Channel:
- Scroll down to the "Channels" section.
- Find "WhatsApp" and click "SET UP CHANNEL".
- Select the Sender Identity (your WhatsApp Sender ID / phone number) from the dropdown list that you provisioned earlier.
- The
Bearer Tokenassociated with that Sender ID should be automatically used or confirmed here as part of the channel configuration. - Click "SAVE".
- Configure Webhooks:
- Scroll to the "Webhooks" section.
- Click "ADD WEBHOOK".
- Target URL: Enter the publicly accessible URL for your webhook handler. This should be the value of
NEXT_PUBLIC_APP_URLfrom your environment variables, followed by the API route path:${NEXT_PUBLIC_APP_URL}/api/sinch-webhook.- For local development, use
ngrok. Startngrok http 3000and use the providedhttps://URL (e.g.,https://your-ngrok-subdomain.ngrok-free.app/api/sinch-webhook). UpdateNEXT_PUBLIC_APP_URLin.env.localtemporarily. - For production (e.g., Vercel), use the deployment URL (e.g.,
https://your-app.vercel.app/api/sinch-webhook). EnsureNEXT_PUBLIC_APP_URLis set correctly in your deployment environment variables.
- For local development, use
- Triggers: Select the events you want to receive. At a minimum, choose:
MESSAGE_INBOUND(for incoming messages)MESSAGE_DELIVERY(for delivery receipts)
- Add others like
CONTACT_CREATE_EVENT,CONVERSATION_START_EVENT, etc., based on your needs. - Secret: Optionally add a secret token here. If you do, implement signature verification logic in your
sinch-webhookroute using this secret (refer to Sinch documentation). - Click "ADD".
Follow the dashboard navigation paths precisely to find these settings.
What Error Handling Best Practices Should You Implement?
Robust error handling is crucial for production WhatsApp messaging systems.
Error Handling Strategy
| Component | Strategy |
|---|---|
| API Routes | Use try...catch blocks in both send-whatsapp and sinch-webhook routes |
| Validation | Validate input early (request bodies, phone number formats) |
| Sinch API Responses | Check response.ok and parse error details from the JSON body returned by Sinch on failure. Log these details. |
| Webhook Responses | Return 200 OK quickly. For processing errors, log internally and return a generic 500 error to Sinch without revealing internal details. |
Logging Best Practices
- Use
console.log,console.warn, andconsole.errorstrategically. - Log key events: sending requests, receiving webhooks, successful operations, errors, and relevant data (like message IDs, contact IDs, error messages from Sinch).
- In production, use a dedicated logging service (like Vercel Logs, Datadog, Sentry) for structured logging, searching, and alerting.
Retry Mechanisms
For transient network errors or temporary Sinch API issues (e.g., 5xx errors) when sending messages, implement a retry strategy.
Caution: Avoid retrying blindly for client-side errors (4xx errors like invalid input). Retry primarily for server-side issues (5xx errors) or network timeouts.
Example using async-retry library:
First, install the library:
npm install async-retryThen, integrate it into your sending logic:
// Inside app/api/send-whatsapp/route.js (conceptual integration)
import { NextResponse } from 'next/server';
import retry from 'async-retry';
// ... getSinchAuthToken function ...
export async function POST(request) {
// ... input validation ...
// ... variable setup (projectId, sinchApiUrl, payload) ...
try {
// Wrap the fetch call within the retry block
const result = await retry(
async (bail, attempt) => {
console.log(`Attempting Sinch API call (attempt ${attempt})...`);
const response = await fetch(sinchApiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': getSinchAuthToken()
},
body: JSON.stringify(payload),
});
if (!response.ok) {
const status = response.status;
// Don't retry on client errors (4xx)
if (status >= 400 && status < 500) {
// Use bail to stop retrying and propagate the error immediately
const errorData = await response.json();
const clientError = new Error(`Sinch API client error: ${status}`);
clientError.originalStatus = status;
clientError.details = errorData;
bail(clientError); // Stop retrying
return; // Bail prevents further execution here
}
// For server errors (5xx) or other network issues, throw to trigger retry
const serverError = new Error(`Sinch API server error or network issue: ${status}`);
serverError.originalStatus = status;
try { serverError.details = await response.json(); } catch { /* ignore parsing error */ }
throw serverError; // Trigger retry
}
// If response is OK, parse and return the data
const responseData = await response.json();
console.log('Sinch Send Success within retry block:', responseData);
return responseData; // Return successful data
},
{
retries: 3, // Number of retries
factor: 2, // Exponential backoff factor
minTimeout: 1000, // Initial timeout in ms (1 second)
onRetry: (error, attempt) => {
console.warn(`Retrying Sinch API call (attempt ${attempt}) due to: ${error.message}`);
}
}
);
// If retry succeeded, 'result' holds the responseData
return NextResponse.json({ success: true, messageId: result.message_id });
} catch (error) {
// This catches errors after all retries failed, or if bail was called
console.error('Error sending WhatsApp message after retries:', error.message);
// Log details if available (e.g., from clientError or last serverError)
const status = error.originalStatus || 500;
const details = error.details || null;
return NextResponse.json(
{ error: 'Failed to send message via Sinch after retries.', details: details },
{ status: status }
);
}
}Integrate this retry logic carefully into your existing send-whatsapp route, replacing the simple fetch call.
Should You Store WhatsApp Message History in a Database?
While not required for basic sending/receiving, storing message history or contact information is common. Prisma is a popular choice with Next.js for database operations.
For a complete guide on implementing database storage with Prisma, including schema design and integration examples, see our Next.js database integration tutorial.
How to Set Up Prisma for Message Storage
npm install prisma --save-dev
npm install @prisma/client
npx prisma init --datasource-provider postgresql # Or your preferred DBConfigure your database connection URL in .env.
Example Database Schema for WhatsApp Messages
File: prisma/schema.prisma
// prisma/schema.prisma
datasource db {
provider = "postgresql" // Or "mysql", "sqlite", "sqlserver", "mongodb"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Contact {
id String @id @default(cuid())
phone String @unique // E.164 format
name String?
createdAt DateTime @default(now())
messages Message[]
}
model Message {
id String @id @default(cuid())
sinchMessageId String? @unique // ID from Sinch API response/webhook
contactId String
contact Contact @relation(fields: [contactId], references: [id])
appId String? // Sinch App ID associated with the message
direction String // "INBOUND" or "OUTBOUND"
channel String // e.g., "WHATSAPP"
status String? // e.g., "SENT", "DELIVERED", "READ", "FAILED", "RECEIVED"
content Json // Store text, media URLs, templates etc.
timestamp DateTime // Time of the event (from Sinch or DB creation)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
reason String? // Failure reason from delivery receipt
}Apply Schema & Generate Client
npx prisma migrate dev --name init # Creates migration and applies to DB
npx prisma generate # Generates Prisma ClientUsage in API Routes
// Example in app/api/sinch-webhook/route.js
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// ... inside POST function, after parsing payload ...
if (payload.message_inbound_event) {
const event = payload.message_inbound_event;
const messageData = event.message;
const contactPhone = event.contact_id; // Assuming this is the E.164 phone
const sinchMsgId = event.message_id; // Check if inbound events provide a unique ID
// Perform DB operations asynchronously (move outside the immediate response if heavy)
try {
await prisma.message.create({
data: {
sinchMessageId: sinchMsgId, // Ensure this is unique or handle conflicts
contact: {
connectOrCreate: {
where: { phone: contactPhone },
create: { phone: contactPhone },
},
},
appId: event.app_id,
direction: 'INBOUND',
channel: event.channel, // Make sure channel is in the event payload
content: messageData, // Store the whole message object
timestamp: new Date(event.accepted_time), // Use Sinch timestamp
status: 'RECEIVED', // Custom status
},
});
console.log(`Saved inbound message ${sinchMsgId} from ${contactPhone}`);
} catch (dbError) {
console.error('Database error saving inbound message:', dbError);
// Decide how to handle DB errors – perhaps queue for later retry?
}
} else if (payload.message_delivery_event) {
const event = payload.message_delivery_event;
const sinchMsgId = event.message_id;
// Perform DB operations asynchronously
try {
const updatedMessage = await prisma.message.update({
where: { sinchMessageId: sinchMsgId },
data: {
status: event.status,
reason: event.reason,
timestamp: new Date(event.processed_time), // Use Sinch timestamp
},
});
console.log(`Updated status for message ${sinchMsgId} to ${event.status}`);
} catch (dbError) {
// Handle cases where the message might not exist (e.g., if outbound save failed)
if (dbError.code === 'P2025') { // Prisma code for RecordNotFound
console.warn(`Message with sinchMessageId ${sinchMsgId} not found for status update.`);
} else {
console.error('Database error updating message status:', dbError);
}
// Decide how to handle DB errors
}
}
// Handle prisma client connection properly (e.g., singleton pattern)
// https://www.prisma.io/docs/guides/database/troubleshooting-orm/help-articles/nextjs-prisma-client-dev-practices