Node.js
Next.js Integration
Build a full-stack Next.js app with Sendable WhatsApp API.
Project Setup
npx create-next-app@latest my-sendable-app --typescript --tailwind --app
cd my-sendable-appEnvironment Setup
Create .env.local:
SENDABLE_API_KEY=your_api_key_here
SENDABLE_WEBHOOK_SECRET=your_webhook_secretAPI Route for Sending Messages
Create app/api/send-message/route.ts:
import { NextRequest, NextResponse } from 'next/server'
export async function POST(req: NextRequest) {
try {
const { to, text } = await req.json()
const response = await fetch('https://api.sendable.dev/messages/send', {
method: 'POST',
headers: {
'x-api-key': process.env.SENDABLE_API_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify({ to, text }),
})
if (!response.ok) {
const error = await response.json()
return NextResponse.json(error, { status: response.status })
}
const data = await response.json()
return NextResponse.json(data)
} catch (error) {
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}Webhook Handler
Create app/api/webhook/route.ts:
import { NextRequest, NextResponse } from 'next/server'
import { Sendable } from '@sendable-dev/sdk'
const sendable = new Sendable({
apiKey: process.env.SENDABLE_API_KEY!,
apiKeyScope: 'session',
webhookSecret: process.env.SENDABLE_WEBHOOK_SECRET,
})
export async function POST(req: NextRequest) {
const payload = await req.text()
let event
try {
event = await sendable.webhooks.verifyAndParse(payload, req.headers)
} catch {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
}
// Handle different event types
switch (event.type) {
case 'message.delivered':
console.log(`Message ${event.data.id} delivered`)
break
case 'message.failed':
console.log(`Message ${event.data.id} failed with status ${event.data.status}`)
break
case 'message.received':
console.log(`Received message from ${event.data.from}:`, event.data.text?.content)
break
}
return NextResponse.json({ received: true })
}Frontend Component
Create app/components/MessageForm.tsx:
'use client'
import { useState } from 'react'
export function MessageForm() {
const [to, setTo] = useState('')
const [text, setText] = useState('')
const [status, setStatus] = useState('')
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setStatus('Sending...')
try {
const res = await fetch('/api/send-message', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ to, text }),
})
if (res.ok) {
setStatus('Message sent!')
setTo('')
setText('')
} else {
const error = await res.json()
setStatus(`Error: ${error.message}`)
}
} catch {
setStatus('Failed to send message')
}
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium">Phone Number</label>
<input
type="tel"
value={to}
onChange={(e) => setTo(e.target.value)}
placeholder="6281234567890"
className="mt-1 block w-full rounded-md border p-2"
required
/>
</div>
<div>
<label className="block text-sm font-medium">Message</label>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
className="mt-1 block w-full rounded-md border p-2"
rows={4}
required
/>
</div>
<button
type="submit"
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700"
>
Send Message
</button>
{status && <p className="text-sm text-gray-600">{status}</p>}
</form>
)
}Main Page
Update app/page.tsx:
import { MessageForm } from './components/MessageForm'
export default function Home() {
return (
<main className="max-w-md mx-auto mt-10 p-6">
<h1 className="text-2xl font-bold mb-6">Send WhatsApp Message</h1>
<MessageForm />
</main>
)
}Deployment
- Deploy to Vercel:
vercel- Add environment variables in Vercel dashboard
- Update webhook URL in Sendable dashboard to point to your deployed
/api/webhookendpoint
Complete Features
This implementation includes:
- Server-side API calls (API key never exposed to client)
- Svix webhook verification with the SDK
- Form handling with loading states
- Error handling and user feedback