Send WhatsApp Messages from Next.js API Routes on Vercel
Next.js on Vercel is a common place to wire up WhatsApp notifications: a form submission, an order confirmation, a support ticket update. The tricky part isn't the HTTP call — it's making that call reliably from a serverless function, and receiving webhooks back on a platform with no persistent server. This guide covers both.
Sending a message from an API route
A Route Handler (the app/api/*/route.ts convention) is the simplest place to call the textmeflow API. It runs as a short-lived serverless function, so keep the request synchronous and return a response once the API call settles — don't fire-and-forget without awaiting it, or Vercel can freeze the function before the request completes.
// app/api/notify/route.ts
export async function POST(req: Request) {
const { phone, message } = await req.json();
const res = await fetch('https://api.textmeflow.eu/v1/messages', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.TEXTMEFLOW_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ to: phone, text: message }),
});
if (!res.ok) {
const error = await res.json();
return Response.json({ error }, { status: res.status });
}
return Response.json({ sent: true });
}
Store TEXTMEFLOW_API_KEY as a Vercel environment variable, not in code — it should never be exposed to the client bundle, so only reference it inside a Route Handler or Server Action, never in a 'use client' component.
Calling it from a Server Action
If you're already on the App Router, a Server Action skips the extra network hop of calling your own API route from the client:
// app/actions.ts
'use server';
export async function sendConfirmation(phone: string, orderId: string) {
const res = await fetch('https://api.textmeflow.eu/v1/messages', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.TEXTMEFLOW_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: phone,
text: `Your order ${orderId} is confirmed.`,
}),
});
return res.ok;
}
Call this directly from a form's action prop or after a mutation completes — no client-side fetch, no API route to maintain.
Receiving webhooks on Vercel
If you also want delivery status or inbound replies, you need a Route Handler that accepts POST requests from textmeflow and verifies the signature before trusting the payload. Every webhook request carries an HMAC signature — see the webhook docs for the exact header and verification steps. Skipping that check means anyone who finds your endpoint URL can post fake delivery events.
// app/api/webhooks/textmeflow/route.ts
import crypto from 'crypto';
export async function POST(req: Request) {
const rawBody = await req.text();
const signature = req.headers.get('x-textmeflow-signature') ?? '';
const expected = crypto
.createHmac('sha256', process.env.TEXTMEFLOW_WEBHOOK_SECRET!)
.update(rawBody)
.digest('hex');
if (signature !== expected) {
return new Response('Invalid signature', { status: 401 });
}
const event = JSON.parse(rawBody);
// handle event.type: message.delivered, message.failed, message.received, ...
return new Response('ok', { status: 200 });
}
Two serverless-specific details matter here. First, don't use req.json() before verifying the signature — you need the exact raw bytes textmeflow signed, and re-serializing a parsed object can produce a different byte string that fails verification. Second, Vercel functions have a cold-start and execution-time budget: keep webhook handling fast (log, queue, acknowledge) and push slow work like sending a follow-up message or writing to a database to a background job or queue, rather than blocking the response.
Idempotency on retries
textmeflow retries a webhook delivery if your endpoint doesn't respond in time, which on a cold-starting serverless function happens more often than on a warm server. Each event carries an event_id — store processed IDs (even just in a KV store or your database) and skip duplicates, so a retried message.delivered event doesn't trigger a duplicate notification downstream. The webhook retry and idempotency guide covers the pattern in more detail.
Getting started
You only need an API key to start sending: pair your WhatsApp number through the QR flow, grab the key from the dashboard, and the Route Handler above is a working integration in a few minutes. See the getting started guide for the pairing step, then try textmeflow free — the free plan includes 50 messages a month to test the whole flow before you commit to anything.
Zelf WhatsApp-berichten versturen via API?
Gratis voor altijd tot 50 berichten/maand. QR scannen en binnen 5 minuten verstuur je je eerste bericht.
Gratis voor altijd