This guide walks you through building a production-ready Node.js backend that receives InstantDM webhook events and sends Instagram DMs via the API. Use this as a foundation for any custom integration.
Project Setup
mkdir instantdm-backend && cd instantdm-backend
npm init -y
npm install express dotenv
Create .env:
INSTANTDM_API_KEY=your_api_key_here
WEBHOOK_SECRET=your_webhook_secret
PORT=3000
Complete Backend
// server.js
require('dotenv').config();
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const API_KEY = process.env.INSTANTDM_API_KEY;
const API_URL = 'https://api.instantdm.com/api-webhook';
// Verify webhook signature
function verifySignature(req) {
const signature = req.headers['x-webhook-signature'];
if (!signature) return false;
const expected = crypto
.createHmac('sha256', API_KEY)
.update(JSON.stringify(req.body))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Send a DM via InstantDM API
async function sendDM(recipientId, message, type = 'text', extra = {}) {
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
action: 'send_message',
type,
recipient_id: recipientId,
message,
...extra,
}),
});
return response.json();
}
// Webhook endpoint
app.post('/webhook', async (req, res) => {
// Optional: verify signature
// if (!verifySignature(req)) return res.status(401).json({ error: 'Invalid signature' });
const { event, timestamp, data } = req.body;
console.log(`[${timestamp}] Event: ${event} from @${data.username}`);
try {
switch (event) {
case 'flow_completed':
await handleFlowCompleted(data);
break;
case 'comment':
await handleComment(data);
break;
case 'dm_received':
await handleDMReceived(data);
break;
default:
console.log(`Unhandled event: ${event}`);
}
res.status(200).json({ status: 'ok' });
} catch (error) {
console.error('Error processing webhook:', error);
res.status(500).json({ status: 'error' });
}
});
async function handleFlowCompleted(data) {
const { username, flow_name, response_variables } = data;
console.log(`Flow "${flow_name}" completed by @${username}`);
// Example: save to database, send to CRM, etc.
// await saveLeadToDatabase(data);
// Send a follow-up DM
await sendDM(
data.instagram_user_id,
`Thanks ${response_variables?.full_name || 'there'}! We received your info and will follow up soon.`
);
}
async function handleComment(data) {
console.log(`Comment from @${data.username}: "${data.comment_text}"`);
// Process comment - keyword detection, logging, etc.
}
async function handleDMReceived(data) {
console.log(`DM from @${data.username}: "${data.message_text}"`);
// Process incoming DM - AI response, routing, etc.
}
// Health check
app.get('/health', (req, res) => res.json({ status: 'ok' }));
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Sending Different Message Types
// Text message
await sendDM(userId, 'Hello!');
// Buttons
await sendDM(userId, 'Choose an option:', 'buttons', {
buttons: [
{ title: 'Pricing', payload: 'pricing' },
{ title: 'Demo', url: 'https://yoursite.com/demo' },
],
});
// Quick replies
await sendDM(userId, 'Are you interested?', 'quick_replies', {
quick_replies: [
{ title: 'Yes', payload: 'yes' },
{ title: 'No', payload: 'no' },
],
});
// Image
await sendDM(userId, '', 'image', {
image_url: 'https://yoursite.com/product.jpg',
});
Deployment
Docker
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Deploy to Railway, Render, or Fly.io
All three platforms support Node.js apps with automatic HTTPS. Push your code to GitHub and connect the repo.
What's Next
- Read the Python backend guide if you prefer Python.
- Deploy to AWS Lambda for serverless.
- Connect to PostgreSQL for data persistence.
- Explore the full API docs at instantdm.com/instagram-api-docs.