Skip to content

Next.js

In a Next.js app the API key must stay on the server: call WhatsTIG from Route Handlers or Server Actions, never from client components. The SDK is @whatstig/sdk (see Node.js).

// lib/whatstig.ts  (server-only)
import 'server-only';
import { WhatsTig } from '@whatstig/sdk';

export const wt = new WhatsTig({ apiKey: process.env.WHATSTIG_API_KEY!, baseUrl: process.env.WHATSTIG_BASE_URL });

Connect screen

// app/api/whatsapp/connect/route.ts
import { NextResponse } from 'next/server';
import { wt } from '@/lib/whatstig';
import { requireTenantAdmin } from '@/lib/auth'; // your own authentication

export async function POST() {
  const tenant = await requireTenantAdmin();
  const ref = `tenant-${tenant.id}`;
  const instance =
    (await wt.instances.findByExternalReference(ref)) ??
    (await wt.instances.create({ name: `${tenant.name} WhatsApp`, externalReference: ref }, { idempotencyKey: `create-${ref}` }));
  await wt.instances.connect(instance.id, { method: 'qr' });
  return NextResponse.json({ instanceId: instance.id });
}
// app/api/whatsapp/status/route.ts
import { NextResponse } from 'next/server';
import { WhatsTigError } from '@whatstig/sdk';
import { wt } from '@/lib/whatstig';
import { requireTenantAdmin } from '@/lib/auth';

export async function GET() {
  const tenant = await requireTenantAdmin();
  const instance = await wt.instances.findByExternalReference(`tenant-${tenant.id}`);
  if (!instance) return NextResponse.json({ status: 'NOT_CREATED' });
  const status = await wt.instances.status(instance.id);
  let qrDataUrl: string | null = null;
  if (status.status === 'QR_REQUIRED') {
    qrDataUrl = await wt.instances.qr(instance.id).then((p) => p.qrDataUrl).catch((e) => {
      if (e instanceof WhatsTigError && e.code === 'QR_NOT_AVAILABLE') return null;
      throw e;
    });
  }
  return NextResponse.json({ status: status.status, reasonCode: status.reasonCode, qrDataUrl }, { headers: { 'Cache-Control': 'no-store' } });
}
// app/settings/whatsapp/connect.tsx
'use client';
import { useEffect, useState } from 'react';

export function ConnectWhatsApp() {
  const [state, setState] = useState<{ status: string; qrDataUrl?: string | null }>({ status: 'NOT_CREATED' });
  useEffect(() => {
    const t = setInterval(async () => setState(await (await fetch('/api/whatsapp/status')).json()), 2500);
    return () => clearInterval(t);
  }, []);
  if (state.status === 'CONNECTED') return <p>WhatsApp connected.</p>;
  return (
    <div>
      {state.qrDataUrl ? <img src={state.qrDataUrl} alt="Scan with WhatsApp" width={256} height={256} /> : <p>{state.status}</p>}
      <button onClick={() => fetch('/api/whatsapp/connect', { method: 'POST' })}>Connect WhatsApp</button>
    </div>
  );
}

Sending from a Server Action

'use server';
import { wt } from '@/lib/whatstig';

export async function notifyGuardian(instanceId: string, phone: string, text: string, notificationId: string) {
  return wt.messages.send(
    { instanceId, recipient: phone, type: 'text', content: { text }, clientReference: notificationId },
    { idempotencyKey: notificationId },
  );
}

Webhooks in a Route Handler

// app/api/whatstig/webhooks/route.ts
import { verifyWebhook, WebhookVerificationError } from '@whatstig/sdk';

export async function POST(req: Request) {
  const raw = await req.text(); // raw body, before JSON parsing
  try {
    const event = verifyWebhook(raw, req.headers.get('WhatsTIG-Signature'), process.env.WHATSTIG_WEBHOOK_SECRET!);
    await enqueue(event); // dedupe on event.id
    return new Response(null, { status: 200 });
  } catch (err) {
    return new Response(null, { status: err instanceof WebhookVerificationError ? 401 : 500 });
  }
}

export const runtime = 'nodejs'; // the SDK uses node:crypto