Message lifecycle and webhooks
Handle asynchronous delivery, scheduled replies, Instagram messaging windows, and conversation webhook events.
Message states
| Direction | State | Meaning |
|---|---|---|
| Inbound | received | A message from the contact has arrived. |
| Inbound | read | Your application has marked the conversation as read. |
| Outbound | pending | The reply is queued, including replies scheduled for later. |
| Outbound | sent | The reply was sent through the underlying platform. |
| Outbound | read | The platform reported that the contact read the reply. |
| Outbound | failed | Delivery failed; inspect the message's error field. |
Send a message returns 202 Accepted with a pending message. Use its id to correlate message.sent or message.failed events, or find it in List messages. Do not treat acceptance as successful delivery or automatically send another copy while the first message is pending.
Text and media
Send non-blank content, at least one URL in media_urls, or both. Media URLs must be accessible to the delivery service. To prepare hosted media, see the Media API.
{
"content": "Here is the image you requested.",
"media_urls": ["https://your-cdn.example.com/product.jpg"]
}Request bodies use media_urls and scheduled_at; message response objects expose mediaUrls and scheduledAt.
Messaging windows and scheduling
Instagram's standard reply window ends 24 hours after the contact's last inbound message (lastInboundAt). Your own outbound replies do not extend this window.
For an eligible human support reply, pass:
{
"content": "I have checked your support request.",
"instagram": { "tag": "HUMAN_AGENT" }
}HUMAN_AGENT extends the window to seven days after the contact's last message. It is for a human replying to a support issue and requires App Review plus business verification on the Meta app that owns the connection. It is not a general-purpose extension for automated replies.
To schedule a reply, add scheduled_at with a future ISO 8601 timestamp. Calculate it from your intended delivery time and the contact's current messaging window. The API checks the window against that scheduled time immediately: a reply scheduled beyond the window is rejected with 422, even if the request is made while the window is still open. An invalid or non-future timestamp returns 400.
Scheduled messages remain pending until delivery. The delivery outcome arrives through the same webhooks as an immediate reply.
Cancel a scheduled reply
Call Cancel a scheduled message with the conversation ID and message ID. Only a scheduled message that is still pending can be cancelled. Successful cancellation removes the message from the queue and deletes it.
A reply that was not scheduled, or is no longer pending, returns 409. A message not found in the conversation returns 404. Refresh the message list if cancellation races with delivery.
Pagination and read state
Both list endpoints return data and pagination. When hasMore is true, pass the returned nextCursor as the next request's cursor, retaining your filters. Treat the cursor as opaque.
Conversations are ordered by most recent activity; messages are ordered newest first. Conversation pages default to 25 items (maximum 100); message pages default to 50 items (maximum 200). The message list supports an inbound or outbound direction filter.
Mark conversation as read updates inbound messages and clears the unread count.
Webhook events
Configure subscriptions using the shared Webhooks guide. All events have the standard event, timestamp, and data envelope.
| Event | When it occurs | Fields in data |
|---|---|---|
conversation.started | A new conversation is stored from a platform message. | conversationId, orgId, network, participantId |
message.received | An inbound message is stored. | conversationId, messageId, orgId, network, content, senderId, sentAt |
message.sent | An outbound message is sent or ingested from a platform echo. | conversationId, messageId, orgId, network, platformMessageId |
message.failed | An accepted outbound message fails delivery. | conversationId, messageId, orgId, network, error |
conversationId, messageId, orgId, network |
For example, an inbound message event:
{
"event": "message.received",
"timestamp": "2026-09-21T10:00:01.000Z",
"data": {
"conversationId": "9dyJS",
"messageId": "3kPqR",
"orgId": "org_example",
"network": "instagram",
"content": "Can you help with my order?",
"senderId": "17841400000000000",
"sentAt": "2026-09-21T10:00:00.000Z"
}
}An asynchronous delivery failure uses the same envelope:
{
"event": "message.failed",
"timestamp": "2026-09-21T10:01:00.000Z",
"data": {
"conversationId": "9dyJS",
"messageId": "4rStU",
"orgId": "org_example",
"network": "instagram",
"error": "Example delivery error from the platform"
}
}Webhook payloads are event notifications, not complete message objects. In particular, message.received does not include mediaUrls; fetch the messages endpoint for current message details. Media-only messages can have null content.
Verify signatures against the raw request body, acknowledge promptly with a 2xx response, and handle duplicate notifications idempotently. Use the message ID and event type to correlate updates, and fetch the latest message state when reconciling delayed events. See Delivery and retries.
Handling request failures
Handle validation errors (400), missing conversations or messages (404), cancellation conflicts (409), and expired messaging windows (422) separately from asynchronous message.failed events. Requests rejected before acceptance do not represent a pending message to track. Correct the request or wait for a new inbound message as appropriate, instead of retrying an expired-window reply unchanged.