Instagram Messaging API: a technical guide to Direct messages
The Instagram Messaging API lets a professional account receive Direct messages through webhooks and reply with POST /<IG_ID>/messages. The customer must write first; you then have 24 hours to reply, or up to 7 days for a human agent using the HUMAN_AGENT tag, which requires App Review. Instagram Login needs instagram_business_manage_messages; Facebook Login goes through the Messenger Platform.
Updated:
How does the Instagram DM API work?
The Instagram user always starts the conversation — from the profile, a post, a story mention or another entry point. Meta sends a messages webhook containing the sender's Instagram-scoped ID (IGSID). Your server replies to that IGSID through the Send API within 24 hours. History comes from the Conversations API, and profile details from the User Profile API once the user has messaged you.
This article covers the API mechanics. What is allowed from a business and sales angle is covered in the Instagram Direct automation article; what Socialift adds on top of the raw endpoints is on the Instagram Direct API page.
Requirements by login type
| Instagram Login | Facebook Login (Messenger Platform) | |
|---|---|---|
| Host | graph.instagram.com | graph.facebook.com |
| Token | Instagram User access token | Page access token (from a person with the MESSAGE task) |
| Permissions | instagram_business_basic, instagram_business_manage_messages | instagram_basic, instagram_manage_messages, pages_manage_metadata |
| Send | POST /<IG_ID>/messages | POST /<PAGE_ID>/messages |
| List conversations | GET /me/conversations | GET /<PAGE_ID>/conversations?platform=instagram |
| Webhook subscription | POST /me/subscribed_apps | instagram object in the App Dashboard + subscription of the linked Page |
| With Standard Access | Only accounts you own or manage | Messages only to people with a role on the app |
Notes and limits
On the Facebook Login path Meta also requires the "Allow access to messages" toggle in the Instagram app (Settings → Messages and story replies → Message controls → Connected tools).
The 24-hour window and the HUMAN_AGENT tag
Your app can message an Instagram user only after that user has messaged the professional account. From the user's last message you have 24 hours to respond, and inside that window the reply may contain promotional content.
If someone on your team needs more time — the business is closed for the weekend or the issue takes longer than a day — the reply can go out with the HUMAN_AGENT message tag up to 7 days after the user's message. The tag is meant for manual replies by a human agent, and the Human Agent feature requires App Review and business verification.
Outside these windows there is no way to reach the user through the API: One-Time Notifications, News Messaging and Sponsored Messages from Messenger are not available for the Instagram Messaging API.
In the Socialift SDK, send_message sends a standard text reply without tags. If the window has closed, Meta rejects the call and the SDK raises PermissionError — catch it and route the conversation to a person instead of retrying.
Webhook fields for Instagram messaging
Subscribe only to fields you actually process. With Instagram Login they require instagram_business_basic and instagram_business_manage_messages, and the app must be in Live mode.
| Field | When it fires |
|---|---|
| messages | A user sends a message; the payload holds sender.id (IGSID), recipient.id, timestamp, message.mid and text or attachments |
| message_echoes | The business account sends a message (Instagram Login field); on the Messenger Platform path these arrive in messages with is_echo |
| message_reactions | A user reacts to a message |
| messaging_postbacks | A user taps an icebreaker or a button |
| messaging_seen | A user reads a message |
| messaging_referral | A user opens the conversation from an ig.me link |
| message_edit | A user edits a message (available since September 10, 2025) |
Receiving messages: a webhook handler
Socialift's webhook helpers verify X-Hub-Signature-256, flatten both payload formats and build a stable dedupe key.
import json, os
from socialift.webhooks import dedupe_key, parse_events, verify_request
APP_SECRET = os.environ["WEBHOOK_APP_SECRET"] # secret of the app receiving the webhook
def handle_instagram_webhook(raw_body: bytes, headers, seen: set) -> int:
if not verify_request(APP_SECRET, raw_body, headers):
return 403
for event in parse_events(json.loads(raw_body)):
key = dedupe_key(event)
if event.field != "messages" or key in seen:
continue
seen.add(key)
message = event.value.get("message") or {}
if message.get("is_echo"):
continue # our own outgoing message
igsid = event.value["sender"]["id"]
print("DM from", igsid, message.get("text")) # enqueue, don't process inline
return 200Return 200 fast and move processing to a queue: Meta's Messenger Platform docs expect a 200 OK within 5 seconds and disable a webhook whose deliveries keep failing for an hour.
seen stands in for a unique column in your database — Meta may deliver the same event more than once.
Reading conversations and replying with the SDK
import asyncio, os
from socialift import Instagram, PermissionError as MetaPermissionError
async def main():
token = os.environ["IG_USER_TOKEN"]
async with Instagram.from_env() as ig:
chats = await ig.conversations(token, limit=10)
for chat in chats["data"]:
contact = chat["contact"] or {}
history = await ig.messages(chat["id"], token, limit=20)
print(contact.get("username"), len(history["data"]))
igsid = os.environ["CUSTOMER_IGSID"]
print(await ig.contact(igsid, token))
try:
await ig.send_message(igsid, token, "Thanks! A manager will reply shortly.")
except MetaPermissionError:
print("24-hour window closed: hand over to a human agent")
asyncio.run(main())conversations returns data (each item with id and contact) and paging; pass after= for the next page. With enrich=True (the default) participant profiles are fetched in parallel.
contact(igsid, token) calls the User Profile API and returns available: False instead of raising when the profile can't be read.
Facebook Login: Instagram.via_facebook(app_secret=...), then ig.conversations(page_token, page_id=..., ig_user_id=...) and ig.send_message(igsid, page_token, text, page_id=...) — the SDK adds platform=instagram for you.
send_message covers text; attachments, quick replies, templates, message tags and private replies need direct Graph API calls.
Private replies to comments
A private reply sends a Direct message to someone who commented on the account's post, reel, story, Live broadcast or ad post. The call is the same POST /<IG_ID>/messages, but recipient carries a comment_id instead of an IGSID.
Rules: reply within 7 days of the comment (for Live, only while the broadcast is running); only one message per comment; follow-ups are possible only if the user responds, and then within 24 hours. The message lands in the user's Inbox or Requests folder depending on whether they follow the account.
Permissions differ from regular messaging: instagram_business_basic and instagram_business_manage_comments with Instagram Login; instagram_basic, instagram_manage_comments and pages_read_engagement with Facebook Login. The limit is 750 calls per hour for posts and reels and 100 calls per second for Live comments.
Instagram messaging limits
| What | Limit |
|---|---|
| Conversations API | 2 calls per second per professional account |
| Send API: text, links, reactions, stickers | 100 calls per second per account |
| Send API: audio and video | 10 calls per second per account |
| Message text | UTF-8, up to 1000 bytes |
| Images per message | Up to 10 |
| Attachment size | Images 8 MB; audio, video and PDF 25 MB |
| Message details via the Conversations API | Only the 20 most recent messages of a conversation |
| Requests folder | Conversations inactive for 30 days are not returned |
Constraints to design around
- No cold outreach: the user has to write first.
- No group chats — one customer per conversation.
- Inbox folders (Primary, General, Requests) can't be managed through the API.
- Only the 20 most recent messages have details — store incoming webhook messages yourself.
- User Profile API data (name, username, profile_pic, follower_count, is_user_follow_business, is_business_follow_user, is_verified_user) opens only after the user messaged you or tapped an icebreaker; profile_pic URLs expire within a few days.
- If the user blocked the business, their profile can't be read.
- Media and posts shared in messages must belong to the business account.
- Webhooks arrive only while the app is in Live mode.
Frequently asked questions
Can I send the first message to an Instagram user via the API?
No. A conversation begins only when the Instagram user messages the professional account (for private replies, when they comment on its content). Cold outreach and bulk messaging aren't possible through the Instagram Messaging API.
What happens after the 24-hour window closes?
Standard replies are rejected. A human agent can still reply up to 7 days after the user's last message with the HUMAN_AGENT tag, which requires the Human Agent feature to pass App Review. Automated messages don't qualify for that tag.
What are the Instagram DM API rate limits?
The Send API allows 100 calls per second per account for text, links, reactions and stickers, and 10 per second for audio and video. The Conversations API allows 2 calls per second, and private replies 750 per hour on posts and reels.
Do I need a Facebook Page for the Instagram Messaging API?
No. With Instagram Login you call graph.instagram.com with the instagram_business_manage_messages permission. A Page is needed only on the Facebook Login path, where Instagram messaging runs through the Messenger Platform with a Page token.
Why can't I read a customer's profile?
The User Profile API returns data only after the user messaged your account, tapped an icebreaker or used the persistent menu. If they only commented, or blocked you, Meta returns an error — the Socialift SDK's contact method then returns available: False.