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