convoup Docs
Docs / Guides / Next.js Integration

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-here

Step 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/send

React (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:

  1. Backend endpoint receives the request from the frontend
  2. Backend calls the Convoup SDK with the API key from environment variables
  3. 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