How to Send Facebook Messenger Messages via API

Send and receive Facebook Messenger messages programmatically — Page chats, PSIDs, the 24-hour window, attachments, reactions, and read receipts via the Postproxy API.

What the Messenger API covers

Messenger messaging runs through Meta’s Send API, scoped to a Facebook Page: a Page can reply to people who message it, react to their messages, send attachments, and read delivery and read receipts. It cannot start a conversation with someone who has never contacted the Page, and it cannot message personal profiles — Page-to-person only.

Doing this natively means a Meta app with pages_messaging, App Review for that permission, a Page access token per Page, a verified webhook endpoint, and PSID handling. Through Postproxy those are already in place; what you work with is a chat ID and a message body.

The model: chats and messages

A Chat is a conversation between your Facebook Page and one person; Messages inside it are inbound or outbound. Postproxy holds the Page token and the Meta webhook subscription — no Meta app review, no PSID plumbing on your side.

Sends are asynchronous: 202 Accepted with status: "pending", then "published" with external_id once Meta confirms. Failures land in failed_waiting_for_retry (retried with backoff) or failed, with the platform error passed through in error_details.

Send a message

Find the chat, send into it:

Terminal window
# Chats for the Page, most recent activity first
curl "https://api.postproxy.dev/api/profiles/PROFILE_ID/chats" \
-H "Authorization: Bearer YOUR_API_KEY"
# Send
curl -X POST "https://api.postproxy.dev/api/chats/CHAT_ID/messages" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "body": "Your order shipped today — tracking inside the app." }'

If the Page hasn’t messaged this person yet but you know their PSID (Page-scoped ID, e.g. from an earlier webhook), create the chat first — the call is idempotent and returns the existing chat if there is one:

Terminal window
curl -X POST "https://api.postproxy.dev/api/profiles/PROFILE_ID/chats" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "participant_external_id": "PSID", "participant_name": "Jane Doe" }'

The 24-hour window

Meta permits free-form Messenger sends only within 24 hours of the person’s last inbound message. The chat’s last_inbound_at tells you where you stand before sending. The exception is a private reply to a Page post comment, which opens a chat up to 7 days after the comment — it works on Facebook exactly as on Instagram.

What you can send, by how much time has passed since that last inbound message:

Since last inboundFree-form replyHow
Under 24 hoursYesNormal send
24 hours to 7 daysHuman replies onlytag: "HUMAN_AGENT"
Over 7 daysNoWait for the person to write again
Any time, after a commentYes, once per commentPrivate reply

Replying after the window closes

HUMAN_AGENT is Meta’s message tag for a human following up on someone’s own inquiry after the 24-hour window has shut. It extends the reply window to 7 days from the last inbound message:

Terminal window
curl -X POST "https://api.postproxy.dev/api/chats/CHAT_ID/messages" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "body": "Following up on your order.", "tag": "HUMAN_AGENT" }'

The tag is for a human resolving the person’s own question — nothing else. Marketing, offers, re-engagement blasts and automated follow-ups sent under it are policy violations, and the penalty is suspension of that Page’s messaging capability. It applies on Facebook and Instagram, and is ignored on Telegram. Details: the HUMAN_AGENT tag.

Receive messages with webhooks

Inbound messages arrive as message.received events on your webhook endpoint — payload includes the text, attachments (already mirrored to durable storage), and the chat ID to reply into. Messenger also reports lifecycle: message.sent, message.delivered, and message.read map to external_delivered_at / external_read_at on the message.

A reply bot is the webhook handler plus the send call above — match on event.type === "message.received", generate a response, POST it to event.data.object.chat_id.

Attachments

One attachment per send (a Meta Send API limit), as a URL, multipart upload, or base64 — and a send is either text or media, never both:

Terminal window
curl -X POST "https://api.postproxy.dev/api/chats/CHAT_ID/messages" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "media": ["https://yourcdn.com/receipt-4821.pdf"] }'

Files are copied to durable storage before dispatch, so the stored url never expires.

Reactions

React to a customer’s message from the Page:

Terminal window
curl -X POST "https://api.postproxy.dev/api/messages/MESSAGE_ID/react" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reaction": "like" }'

Messenger expects a unicode emoji; named reactions (love, like, dislike, smile, wow, sad, angry) are translated for you, or pass emoji directly to override. A second react replaces the first — Meta doesn’t stack them. Remove with DELETE /api/messages/MESSAGE_ID/unreact.

Routing signals

Chat metadata carries the owning page_id, and each chat gets the participant’s name and avatar fetched from Meta. Follow/verified signals (is_verified_user, is_user_follow_business, follower count) are an Instagram-only feature — Messenger’s PSID lookup doesn’t return them.

What fails, and why

SymptomCause
Send rejected outside 24 hoursWindow closed — use HUMAN_AGENT if a human is replying, otherwise wait
failed_waiting_for_retryTransient Meta error; retried with backoff, no action needed
failed with a Meta error in error_detailsPermanent — the platform’s own message says which rule was hit
Text and media in one sendNot supported; send them as two messages
Second reaction doesn’t appearMeta replaces rather than stacks reactions
No inbound events arrivingPage not subscribed, or the webhook endpoint is not returning 2xx

Messenger vs the other DM platforms

CapabilityFacebookInstagramTelegramBluesky
TextYesYesYesYes
AttachmentsYesYesYesNo
ReactionsYesYesNoNo
Delivery / read receiptsYesYesNoNo
Inbound deliveryWebhookWebhookWebhookPoller (~5 min)

Same endpoints across all four — which is what makes a unified inbox a rendering exercise instead of four integrations. To hand the person tappable options instead of free text, add quick replies and buttons to a send. Full reference: Direct Messages API.

Ready to get started?

Start with our free plan and scale as your needs grow. No credit card required.