Next.js Integration
The Convoup SDK holds a secret apiKey that must never reach the browser. This page shows how to use it safely in a Next.js application.
Security Warning
Never call new Convoup(...) or any client.send*() method in client-side code (Client Components, plain client-side fetch). The API key would be exposed in the browser. Always use a Route Handler as the intermediary.Architecture
graph LR
A[Client Component] -->|Internal API call| B[Route Handler]
B -->|SDK with API key| C[Convoup API]
C --> D[Meta WhatsApp API]
The Client Component calls your own internal endpoint. The Route Handler holds the API key in process.env and calls the SDK.
Step 1: Environment Variables
Add to your .env.local:
.env.local
CONVOUP_API_KEY=your-api-key-here
CONVOUP_WABA_ID=your-waba-id-hereStep 2: Create a Route Handler
TypeScript
// app/api/whatsapp/send/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { Convoup, ConvoupError } from 'convoup';
const client = new Convoup({
apiKey: process.env.CONVOUP_API_KEY!,
wabaId: process.env.CONVOUP_WABA_ID,
});
export async function POST(req: NextRequest) {
try {
const body = await req.json();
const { to, template, code } = body;
if (!to || !template || !code) {
return NextResponse.json(
{ error: 'Missing required fields: to, template, code' },
{ status: 400 }
);
}
const result = await client.sendOtp({
to,
template,
code,
});
return NextResponse.json(result);
} catch (err) {
if (err instanceof ConvoupError) {
return NextResponse.json(
{ error: err.message, code: err.code },
{ status: err.status }
);
}
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}Step 3: Call from a Client Component
TSX
'use client';
import { useState } from 'react';
export function SendOtpButton() {
const [loading, setLoading] = useState(false);
async function handleSend() {
setLoading(true);
try {
const res = await fetch('/api/whatsapp/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
to: '+918851479441',
template: 'test_welcome_template',
code: '123456',
}),
});
const data = await res.json();
if (!res.ok) {
console.error('Error:', data.error);
return;
}
console.log('Sent:', data.messageId);
} finally {
setLoading(false);
}
}
return (
<button onClick={handleSend} disabled={loading}>
{loading ? 'Sending...' : 'Send OTP'}
</button>
);
}Project Structure
structure
app/
api/
whatsapp/
send/
route.ts <- API key lives here
page.tsx <- Client Component calls /api/whatsapp/sendReact (Non-Next.js)
For plain React apps without Next.js, the principle is the same: your backend must hold the API key and expose an internal endpoint. The Client Component never talks to Convoup directly.
The specific backend implementation depends on your stack (Express, Fastify, etc.). The pattern is:
- Backend endpoint receives the request from the frontend
- Backend calls the Convoup SDK with the API key from environment variables
- Backend returns the result to the frontend
Error Handling
The Route Handler catches ConvoupError and returns structured errors to the client:
JSON
{
"error": "Template not found",
"code": "TEMPLATE_NOT_FOUND"
}The Client Component can check res.ok and display appropriate messages.
Next Steps
- Client Constructor - Full SDK initialization options
- Sending OTPs - OTP sending guide
- Error Codes - All error codes and handling
