Engineering

Facebook Comment-to-DM: Send a Private Messenger Reply with an API

Build Facebook comment-to-DM with Outstand's webhooks and Conversations API. Get the exact request, native-post handling, permissions, and Messenger follow-up rules.

Someone comments “GUIDE” on your Facebook Page's post. Your application recognizes the request and sends the download link privately through Messenger. If they answer, your team or application can continue the conversation.

That is Facebook comment-to-DM. Meta calls the first message a private reply. It is permission to answer a specific comment, not permission to start an unrestricted messaging sequence.

With Outstand, the flow uses two API surfaces: receive a comment.received webhook, then call POST /v1/conversations with facebook.comment_id. This guide covers the request, the response, native Facebook posts, permissions, and the boundary between Meta's rules and Outstand's current implementation.

What a Facebook private reply permits

According to Meta's private replies documentation, you can send one private reply within seven days of the original comment. Further conversation becomes available when the person responds, within the 24-hour messaging window.

For a new contact, think of the flow as three states:

  1. Comment received: you have a comment to answer, not an open Messenger session.
  2. Private reply sent: you have used that comment's private reply. Wait for an answer.
  3. Contact replies: you can send ordinary conversation messages within the supported window.

Sending the private reply does not itself open the follow-up window. A link click is not the inbound message this Outstand workflow needs. Include the promised resource in the first message rather than making delivery depend on an unsolicited second message.

Meta also says private replies cannot be sent to another Facebook Page. Build this for people commenting on your connected business Page's posts, not as a way to message arbitrary profiles or Pages.

Are only top-level comments eligible?

No blanket top-level-only rule appears in Meta's current documentation. It explicitly describes people who commented on a post or comment on the business's Facebook Page. Outstand receives both top-level comments and replies, and its Facebook messaging provider does not impose a top-level-only check.

You can still restrict a campaign to top-level comments. In Outstand's webhook, parentCommentId is null for a top-level comment and identifies the parent comment for a reply. That filter is your campaign policy, not a universal Meta restriction. A particular send remains subject to Meta's access and eligibility checks.

Is the first message always plain text?

Through Outstand's current Facebook start-conversation endpoint, yes. Send non-blank content; the Facebook options object takes comment_id. Media, Facebook quick replies, and Facebook buttons are not exposed for this initial send.

That is an Outstand capability boundary. Meta's underlying Messenger private-reply documentation supports more message types and includes a button-template example. Do not confuse the wrapper's request schema with a platform-wide text-only rule.

1. Connect the Page with messaging access

You need a connected Facebook Page, an Outstand API key authorized for conversations:send, and a webhook endpoint subscribed to comment.received and message.received.

Two Facebook permissions are central:

Permission

Role in this flow

pages_messaging

Send the private reply and receive Messenger answers

pages_manage_metadata

Let Outstand subscribe the Page to the comment and messaging webhooks

With Outstand Managed Keys, both scopes are requested by default. With your own Meta app, or BYOK, they are opt-in. A custom scopes list replaces the defaults rather than extending them, so preserve the other scopes your integration needs.

For BYOK, configure the app's feed and messages webhook subscriptions using the per-app callback described in Facebook configuration. Meta also requires a Page access token requested by someone who can perform the Page's MESSAGING task. Standard Access only covers people with an app role; testing with an administrator does not establish access for every customer.

Reconnect an older Page if it lacks these permissions or never receives comments. Reconnecting updates the existing social account instead of requiring a new account ID. Follow the Conversations setup guide for the connection flow.

2. Read the comment webhook correctly

An illustrative Facebook event looks like this:

json
1{
2  "event": "comment.received",
3  "timestamp": "2026-11-12T10:30:00.000Z",
4  "data": {
5    "orgId": "YOUR_ORG_ID",
6    "network": "facebook",
7    "accountId": "7mQ2x",
8    "postId": null,
9    "platformPostId": "122100000000000000_9988776655443322",
10    "isLive": false,
11    "commentId": "122100000000000000_1122334455667788",
12    "parentCommentId": null,
13    "authorId": "123456789012345",
14    "authorUsername": null,
15    "authorName": "Jane Example",
16    "text": "GUIDE please",
17    "author": null
18  }
19}

Map data.accountId to social_account_id, and data.commentId to facebook.comment_id. Facebook does not need author_id in the send request. The comment author's Facebook ID is not the Page-scoped Messenger ID used to identify the conversation participant.

Use text for keyword matching and platformPostId to identify the campaign post. Facebook events supply an author name when available, not an Instagram-style username. Do not use the profile data to infer whether someone follows the Page: Messenger does not expose that relationship here.

Posts published directly on Facebook work too

Outstand's comment webhooks cover posts owned by the connected Page, including posts published outside Outstand. For a post without an Outstand record, postId is null; platformPostId still identifies the Facebook post.

Do not reject every event with a null postId. That would exclude native posts unnecessarily. For a single-post campaign, match the connected account and platformPostId. For an account-wide keyword, filter by account and keyword instead.

This does not mean historical comments are automatically replayed. Build around incoming events after setup, and test with a fresh comment on an existing native post. The private reply does not require importing that post first.

3. Send the private Messenger reply

Use the Start a conversation endpoint:

bash
1curl -X POST https://api.outstand.so/v1/conversations \
2  -H "Authorization: Bearer YOUR_API_KEY" \
3  -H "Content-Type: application/json" \
4  -d '{
5    "social_account_id": "7mQ2x",
6    "content": "Here is the guide you requested: https://example.com/guide. Reply if you have a question.",
7    "facebook": {
8      "comment_id": "122100000000000000_1122334455667788"
9    }
10  }'

Keep the API key on your server. This request uses Outstand's Bearer authentication, not a Facebook Page token supplied by your client.

A successful request returns HTTP 201 with top-level conversation and message objects, not a data wrapper. An abridged response is:

json
1{
2  "success": true,
3  "conversation": {
4    "id": "9dyJS",
5    "socialAccountId": "7mQ2x",
6    "network": "facebook",
7    "participantId": "PAGE_SCOPED_MESSENGER_ID",
8    "lastInboundAt": null,
9    "status": "active"
10  },
11  "message": {
12    "id": "3kPqR",
13    "conversationId": "9dyJS",
14    "direction": "outbound",
15    "content": "Here is the guide you requested: https://example.com/guide. Reply if you have a question.",
16    "status": "sent"
17  }
18}

Store the conversation ID alongside your campaign and comment IDs. If the person already has a conversation with the Page, the private reply joins that thread instead of creating a separate inbox contact.

Treat sent as API status, not proof of an inbox placement, read, download, or sale. The current Facebook provider records a successful Messenger Send API response; do not label that response a read receipt.

4. Make the automation safe to run repeatedly

Outstand supplies the webhook and messaging endpoints. Your application supplies keyword matching, campaign selection, queues, and deduplication. This is not a request to create a hosted automation flow.

Before sending:

  • Verify the webhook signature.
  • Persist or enqueue the verified event, then acknowledge promptly with a 2xx.
  • Require event === 'comment.received' and data.network === 'facebook'.
  • Check that the account and optional campaign post are allowed.
  • Match an explicit keyword against a string text value.
  • Claim a durable job keyed by organization, account, and comment ID.

A whole-word, case-insensitive GUIDE match avoids answering “guidelines.” If you only want top-level comments, add parentCommentId === null deliberately. Outstand already skips comments written by the connected Page itself.

Webhook redelivery and a second comment are different cases. A redelivered event should not create another job. A later matching comment by the same person is a separate comment and may receive its own private reply. Add a per-person campaign cooldown if you want fewer messages.

5. Continue only when the person replies

Subscribe to message.received. Their answer arrives with the conversation ID, sets lastInboundAt, and lets you use Send a message:

bash
1curl -X POST https://api.outstand.so/v1/conversations/9dyJS/messages \
2  -H "Authorization: Bearer YOUR_API_KEY" \
3  -H "Content-Type: application/json" \
4  -d '{"content":"Happy to help. Which section are you working on?"}'

Outstand enforces a 24-hour window from the latest inbound message for ordinary Facebook replies. Scheduled messages must also fall within that window. Before a new contact answers, follow-up sends return 422.

Do not assume an Instagram HUMAN_AGENT option extends Facebook messaging through Outstand. Facebook conversations reject Instagram-specific options. For the related implementation with different addressing and interactive-message support, see Instagram comment-to-DM automation.

Troubleshooting without blind retries

Response

Action

400

Correct the body, including the Facebook options object.

403

Check organization access and the API key's permissions.

404

Check the connected account and whether its token can access the comment.

409

The comment already has a private reply. Stop retrying it.

422

Check comment age or, for follow-ups, the inbound messaging window.

502

Inspect the platform error and fix its cause before retrying.

Errors from the send route use success: false and an error message. A missing webhook is a different problem: check Page selection, pages_manage_metadata, and app subscriptions before debugging your keyword logic.

Test with a second person's account: comment, inspect the private reply, answer it, then send a follow-up. Track matched comments, successful API sends, inbound answers, and resource visits separately. None guarantees the next stage.

Start with one Page, one keyword, and one useful resource. The Outstand comment-to-DM solution and implementation reference provide the next steps without changing the essential rule: answer the comment once, then let the person choose whether to continue.