Sendable Docs
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-app

Environment Setup

Create .env.local:

SENDABLE_API_KEY=your_api_key_here
SENDABLE_WEBHOOK_SECRET=your_webhook_secret

API 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

  1. Deploy to Vercel:
vercel
  1. Add environment variables in Vercel dashboard
  2. Update webhook URL in Sendable dashboard to point to your deployed /api/webhook endpoint

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

On this page