Message lifecycle and webhooks

Handle asynchronous delivery, scheduled replies, Instagram messaging windows, and conversation webhook events.

Message states

DirectionStateMeaning
InboundreceivedA message from the contact has arrived.
InboundreadYour application has marked the conversation as read.
OutboundpendingThe reply is queued, including replies scheduled for later.
OutboundsentThe reply was sent through the underlying platform.
OutboundreadThe platform reported that the contact read the reply.
OutboundfailedDelivery 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.

EventWhen it occursFields in data
conversation.startedA new conversation is stored from a platform message.conversationId, orgId, network, participantId
message.receivedAn inbound message is stored.conversationId, messageId, orgId, network, content, senderId, sentAt
message.sentAn outbound message is sent or ingested from a platform echo.conversationId, messageId, orgId, network, platformMessageId
message.failedAn 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.