Back to Blog

Instagram Messaging API: Complete Developer Guide

Learn how to integrate the Instagram Messaging API to send DMs programmatically, handle webhooks, and build automated messaging flows.

By

+8

Post everywhere. One API.

Try Free

The Instagram Messaging API enables businesses to send and receive direct messages programmatically through Instagram's official Graph API. Whether you're building customer support automation, notification systems, or conversational commerce flows, this guide covers everything you need to integrate the Instagram DM API into your application.

Instagram's messaging capabilities are part of the broader Meta Graph API ecosystem. Unlike unofficial methods that risk account suspension, the official Instagram Direct Message API provides reliable, scalable messaging for business accounts with proper authentication and rate limiting.

Prerequisites and Requirements

Before you can send Instagram DMs programmatically, you need to set up several components. The Instagram Messaging API has specific requirements that differ from other social platforms.

Business Account Requirements

The Instagram Messaging API only works with Instagram Business or Creator accounts connected to a Facebook Page. Personal accounts cannot access the messaging endpoints.

RequirementDetails
Account TypeInstagram Business or Creator account
Facebook PageMust be linked to a Facebook Page
Meta AppRequires a registered Meta Developer App
Permissionsinstagram_business_manage_messages scope
App ReviewRequired for production access beyond test users

Creating a Meta Developer App

  1. Navigate to developers.facebook.com and create a new app
  2. Select "Business" as the app type
  3. Add the "Instagram Graph API" product to your app
  4. Configure your OAuth redirect URIs
  5. Note your App ID and App Secret
// Environment variables needed for Instagram API integration
interface InstagramConfig {
  clientId: string;      // Meta App ID
  clientSecret: string;  // Meta App Secret
  redirectUri: string;   // Your OAuth callback URL
  webhookVerifyToken: string; // For webhook verification
}

const config: InstagramConfig = {
  clientId: process.env.INSTAGRAM_CLIENT_ID || '',
  clientSecret: process.env.INSTAGRAM_CLIENT_SECRET || '',
  redirectUri: process.env.INSTAGRAM_REDIRECT_URI || '',
  webhookVerifyToken: process.env.INSTAGRAM_WEBHOOK_VERIFY_TOKEN || '',
};

Note: Store all credentials in environment variables. Never commit API secrets to version control.

OAuth 2.0 Authentication Flow

The Instagram Messaging API uses OAuth 2.0 for authentication. You'll need to implement a two-step token exchange process to obtain long-lived access tokens.

Step 1: Generate Authorization URL

Direct users to Instagram's authorization page with the required scopes:

function getInstagramAuthUrl(state: string): string {
  const scopes = [
    'instagram_business_basic',
    'instagram_business_content_publish',
    'instagram_business_manage_messages', // Required for DMs
  ];

  const params = new URLSearchParams({
    client_id: config.clientId,
    redirect_uri: config.redirectUri,
    scope: scopes.join(','),
    response_type: 'code',
    auth_type: 'rerequest', // Force re-permission prompt
    state: state, // CSRF protection
  });

  return `https://www.instagram.com/oauth/authorize?${params.toString()}`;
}

Step 2: Exchange Code for Tokens

After the user authorizes your app, Instagram redirects to your callback URL with an authorization code. Exchange this for access tokens:

interface TokenResponse {
  accessToken: string;
  refreshToken: string;
  userId: string;
  expiresIn: number;
}

async function exchangeCodeForToken(
  code: string
): Promise<TokenResponse> {
  // Step 1: Exchange code for short-lived token
  const tokenResponse = await fetch(
    'https://api.instagram.com/oauth/access_token',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({
        client_id: config.clientId,
        client_secret: config.clientSecret,
        grant_type: 'authorization_code',
        redirect_uri: config.redirectUri,
        code: code,
      }),
    }
  );

  if (!tokenResponse.ok) {
    const error = await tokenResponse.text();
    throw new Error(`Token exchange failed: ${error}`);
  }

  const shortLivedData = await tokenResponse.json();
  const shortLivedToken = shortLivedData.access_token;
  const userId = shortLivedData.user_id;

  // Step 2: Exchange for long-lived token (60 days)
  const longLivedResponse = await fetch(
    `https://graph.instagram.com/access_token?` +
    `grant_type=ig_exchange_token&` +
    `client_secret=${config.clientSecret}&` +
    `access_token=${shortLivedToken}`,
    { method: 'GET' }
  );

  if (!longLivedResponse.ok) {
    const error = await longLivedResponse.text();
    throw new Error(`Long-lived token exchange failed: ${error}`);
  }

  const longLivedData = await longLivedResponse.json();

  return {
    accessToken: longLivedData.access_token,
    refreshToken: longLivedData.access_token,
    userId: userId,
    expiresIn: longLivedData.expires_in,
  };
}

Refreshing Access Tokens

Long-lived tokens expire after 60 days. Implement token refresh to maintain access:

async function refreshAccessToken(
  currentToken: string
): Promise<{ accessToken: string; expiresIn: number }> {
  const response = await fetch(
    `https://graph.instagram.com/refresh_access_token?` +
    `grant_type=ig_refresh_token&` +
    `access_token=${currentToken}`,
    { method: 'GET' }
  );

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`Token refresh failed: ${response.status} ${errorText}`);
  }

  const data = await response.json();

  if (data.error) {
    throw new Error(`Token refresh error: ${data.error.message}`);
  }

  if (!data.access_token) {
    throw new Error('Token refresh did not return an access_token');
  }

  return {
    accessToken: data.access_token,
    expiresIn: data.expires_in,
  };
}

Note: Schedule token refresh jobs to run before expiration. A good practice is to refresh tokens when they have less than 7 days remaining.

Setting Up Webhooks for Real-time Messages

To receive incoming messages in real-time, you need to configure webhooks. Instagram sends POST requests to your endpoint whenever a user messages your business account.

Webhook Verification Endpoint

Instagram verifies your webhook URL with a GET request before activating it:

import { Request, Response } from 'express';

function handleWebhookVerification(
  req: Request,
  res: Response
): void {
  const mode = req.query['hub.mode'];
  const token = req.query['hub.verify_token'];
  const challenge = req.query['hub.challenge'];

  if (mode === 'subscribe' && token === config.webhookVerifyToken) {
    console.log('Webhook verified successfully');
    res.status(200).send(challenge);
    return;
  }

  console.error('Webhook verification failed');
  res.status(403).send('Verification failed');
}

Build faster with Late

One API call to post everywhere. No OAuth headaches. No platform-specific code.

Free tier • No credit card • 99.97% uptime

Sending Messages via the Instagram DM API

The core functionality of the Instagram Direct Message API is sending messages to users who have initiated conversations with your business.

Sending Text Messages

To send Instagram DMs programmatically, use the messages endpoint:

interface SendMessageOptions {
  accessToken: string;
  igUserId: string;      // Your Instagram Business Account ID
  recipientId: string;   // The user's Instagram-scoped ID
  text: string;
}

interface SendMessageResponse {
  messageId: string;
}

async function sendTextMessage(
  options: SendMessageOptions
): Promise<SendMessageResponse> {
  const { accessToken, igUserId, recipientId, text } = options;

  const response = await fetch(
    `https://graph.instagram.com/${igUserId}/messages`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${accessToken}`,
      },
      body: JSON.stringify({
        recipient: {
          id: recipientId,
        },
        message: {
          text: text,
        },
      }),
    }
  );

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Failed to send message: ${error}`);
  }

  const data = await response.json();
  return { messageId: data.message_id };
}

Rate Limits and Best Practices

Understanding rate limits is essential for building reliable Instagram messaging integrations.

Rate Limit Overview

Limit TypeThresholdWindow
API Calls200 callsPer hour per user
Messages SentVariesBased on conversation history
Webhook ResponsesMust respond within 20 secondsPer event

Best Practices

  1. Implement exponential backoff for rate limit errors
  2. Cache user tokens to avoid repeated auth calls
  3. Use webhook acknowledgment immediately, process asynchronously
  4. Monitor token expiration and refresh proactively
  5. Log all API interactions for debugging

Using Late for Instagram Messaging Integration

Building and maintaining Instagram Messaging API integrations requires significant development effort. You need to handle OAuth flows, token refresh, webhook management, rate limiting, and error handling across potentially multiple social platforms.

Late provides a unified API that simplifies Instagram messaging integration along with other social platforms. Instead of building separate integrations for each platform, Late offers:

  • Unified Authentication: Single OAuth flow for Instagram, Facebook, Twitter, LinkedIn, and more
  • Automatic Token Management: Late handles token refresh and expiration automatically
  • Webhook Abstraction: Receive normalized webhook events across all platforms
  • Built-in Rate Limiting: Intelligent request queuing and retry logic
  • Type-Safe SDK: Full TypeScript support with comprehensive types

With Late, the Instagram messaging implementation becomes straightforward:

import { Late } from '@getlate/sdk';

const late = new Late({ apiKey: process.env.LATE_API_KEY });

// Send a message through Late's unified API
await late.messages.send({
  accountId: 'instagram-account-id',
  recipientId: 'user-id',
  content: {
    text: 'Hello from Late!',
  },
});

// Handle incoming messages with normalized events
late.webhooks.on('message.received', (event) => {
  console.log('Platform:', event.platform); // 'instagram'
  console.log('Message:', event.content.text);
});

Late handles the complexity of the Instagram Messaging API so you can focus on building your application's core functionality. Check out the Late documentation to get started with Instagram messaging and other social platform integrations.

Miquel Palet - Author

Written by

Miquel Palet

Founder & CEO

Miquel is the founder of Late, building the most reliable social media API for developers. Previously built multiple startups and scaled APIs to millions of requests.

View all articles

Learn more about Late with AI

See what AI assistants say about Late API and this topic

One API. 13+ platforms.

Ship social media features in minutes, not weeks.

Built for developers. Loved by agencies. Trusted by 6,325 users.