Node.js
Express Integration
Build a REST API with Express and Sendable WhatsApp API.
Project Setup
mkdir sendable-express-api
cd sendable-express-api
npm init -y
npm install express cors dotenv @sendable-dev/sdk
npm install -D @types/express @types/cors @types/node typescript ts-node nodemonTypeScript Configuration
Create tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist"
},
"include": ["src/**/*"]
}Project Structure
src/
├── config/
│ └── sendable.ts
├── routes/
│ ├── messages.ts
│ └── webhooks.ts
├── middleware/
│ └── auth.ts
└── index.tsConfiguration
Create src/config/sendable.ts:
import { Sendable } from '@sendable-dev/sdk'
if (!process.env.SENDABLE_API_KEY) {
throw new Error('SENDABLE_API_KEY is required')
}
export const sendable = new Sendable({
apiKey: process.env.SENDABLE_API_KEY,
apiKeyScope: 'session',
baseUrl: process.env.SENDABLE_API_URL,
webhookSecret: process.env.SENDABLE_WEBHOOK_SECRET,
})Authentication Middleware
Create src/middleware/auth.ts:
import { Request, Response, NextFunction } from 'express'
// Simple API key auth for your own API
export function authMiddleware(req: Request, res: Response, next: NextFunction) {
const apiKey = req.headers['authorization']?.replace('Bearer ', '')
if (apiKey !== process.env.API_KEY) {
return res.status(401).json({ error: 'Unauthorized' })
}
next()
}Messages Route
Create src/routes/messages.ts:
import { Router } from 'express'
import { sendable } from '../config/sendable'
const router = Router()
// Send a message
router.post('/send', async (req, res) => {
try {
const { chatId, text } = req.body
if (!chatId || !text) {
return res.status(400).json({
error: 'chatId and message text are required'
})
}
const response = await sendable.messages.send({
chatId,
text: { content: text },
})
res.json({
success: true,
messageId: response.messageId,
status: response.status
})
} catch (error) {
console.error('Error sending message:', error)
res.status(500).json({ error: 'Failed to send message' })
}
})
// Get message status
router.get('/:messageId/status', async (req, res) => {
try {
const { messageId } = req.params
const message = await sendable.messages.get(messageId)
res.json(message)
} catch (error) {
console.error('Error getting status:', error)
res.status(500).json({ error: 'Failed to get message status' })
}
})
export default routerWebhook Route
Create src/routes/webhooks.ts:
import express from 'express'
import { Router } from 'express'
import { sendable } from '../config/sendable'
const router = Router()
// Webhook handler
router.post('/', express.raw({ type: 'application/json' }), async (req, res) => {
const payload = req.body.toString()
let event
try {
event = await sendable.webhooks.verifyAndParse(payload, req.headers)
} catch {
return res.status(401).json({ error: 'Invalid signature' })
}
// Process event asynchronously
handleWebhookEvent(event).catch(console.error)
// Respond immediately
res.json({ received: true })
})
async function handleWebhookEvent(event: any) {
console.log(`[${new Date().toISOString()}] Event: ${event.type}`)
switch (event.type) {
case 'message.delivered':
// Update database, notify user, etc.
console.log(`Message ${event.data.id} delivered`)
break
case 'message.read':
console.log(`Message ${event.data.id} read by ${event.data.to}`)
break
case 'message.failed':
console.error(`Message ${event.data.id} failed with status ${event.data.status}`)
// Handle failure - retry, notify, etc.
break
case 'message.received':
console.log(`Received from ${event.data.from}: ${event.data.text?.content}`)
// Handle incoming message - auto-reply, store, etc.
break
case 'session.socket-disconnected':
console.error(`Session ${event.data.sessionId} disconnected`)
// Alert admin, attempt reconnect, etc.
break
}
}
export default routerMain Server
Create src/index.ts:
import express from 'express'
import cors from 'cors'
import dotenv from 'dotenv'
dotenv.config()
import messagesRouter from './routes/messages'
import webhooksRouter from './routes/webhooks'
import { authMiddleware } from './middleware/auth'
const app = express()
const PORT = process.env.PORT || 3000
// Middleware
app.use(cors())
// Public webhook route (no auth - uses signature verification)
app.use('/webhook', webhooksRouter)
// JSON parsing for the rest of your API
app.use(express.json())
// Protected API routes
app.use('/api/messages', authMiddleware, messagesRouter)
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() })
})
// Start server
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`)
})Environment Variables
Create .env:
## Sendable
SENDABLE_API_KEY=your_sendable_api_key
SENDABLE_WEBHOOK_SECRET=whsec_your_svix_webhook_secret
## Your API
API_KEY=your_internal_api_key
## Server
PORT=3000Running the Server
## Development
npx nodemon src/index.ts
## Production
npm run build
npm startAPI Usage Examples
Send a message
curl -X POST http://localhost:3000/api/messages/send \
-H "Authorization: Bearer your_internal_api_key" \
-H "Content-Type: application/json" \
-d '{
"to": "6281234567890",
"text": "Hello from Express!"
}'Get message status
curl http://localhost:3000/api/messages/msg_abc123/status \
-H "Authorization: Bearer your_internal_api_key"Production Deployment
- Use a process manager like PM2:
npm install -g pm2
pm2 start dist/index.js --name sendable-api- Set up a reverse proxy with Nginx
- Use HTTPS for webhook endpoints
- Monitor logs and set up alerts