Integration Guide

How to Build a Custom Node.js Backend for InstantDM

Learn how to build a custom Node.js backend that receives InstantDM webhooks and sends Instagram DMs via the API. Complete Express.js example with error handling.

Meta Business Partner
30,000+ creators
$9.99/mo flat

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

Ready to Automate Your Instagram DMs?

Join 30,000+ creators and brands using InstantDM today.

Start Your Free Trial

No credit card required. Setup in under 15 minutes.