Outstand turns an Instagram or Facebook comment into a direct message with one call. The comment.received webhook tells you about the comment, your code decides whether it deserves an answer, and POST /v1/conversations sends the private reply. That is comment to DM automation as infrastructure: no visual flow builder to work around, no separate Meta integration per network, and the conversation stays addressable through the same API after the commenter replies.
Starting a conversation is supported on Instagram and Facebook, so Instagram comment to DM and Facebook comment to DM both run through the same endpoint. Those are the networks where Meta permits a private reply to a comment, and a request against any other connected account is rejected with 400 and the message Conversations are not supported on <network>.
So there is no TikTok, LinkedIn or X comment-to-DM here. Threads is the interesting near miss: Threads replies do arrive on comment.received, so you can read them and act on them however you like, but there is no DM surface to answer them in, and the payload's authorId is null on Threads regardless.
Direct Messages currently require Outstand's Managed Keys rather than your own Meta app, because inbound messages reach us through webhooks on our app. If you are on BYOK and need this, talk to us first rather than building against it. Publishing to Instagram and Facebook is unaffected either way.
Two pieces of API surface, and everything interesting happens in the middle, where your code lives.
Someone commentsOn a post owned by a connected account. Posts published outside Outstand count too.comment.received firesWith the comment text, its author, the post, and whether it is a top-level comment or a reply.Your code decidesKeyword match, database lookup, model call. Nothing about this step is our business.POST /v1/conversationsThe commenter gets the DM, and Outstand stores it as the conversation's first message.They replymessage.received fires and the 24-hour window opens. The thread continues through the same API.It is one conversationA later comment from the same person joins their existing thread instead of starting a new one.// comment.received -> private reply, the whole flow.
// Acknowledge first: the send calls Meta, so do it after you respond.
const KEYWORD = /\blink\b/i;
app.post('/webhooks/outstand', async (req, res) => {
if (!verifySignature(req)) return res.sendStatus(401);
res.sendStatus(200); // ack fast, then work
if (req.body.event !== 'comment.received') return;
const c = req.body.data;
// parentCommentId is null for a top-level comment.
if (c.parentCommentId || !KEYWORD.test(c.text ?? '')) return;
// Instagram needs the author id; Facebook replies to the comment alone.
const addressing =
c.network === 'instagram'
? { instagram: { comment_id: c.commentId, author_id: c.authorId } }
: { facebook: { comment_id: c.commentId } };
await fetch('https://api.outstand.so/v1/conversations', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.OUTSTAND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
social_account_id: c.accountId,
content: `Thanks @${c.authorUsername}! Here it is: https://shop.example.com/blue`,
...addressing,
}),
});
});
// 201 Created returns the conversation and its first message. Store
// conversation.id against c.commentId so the reply lands in the right thread.Three fields off the webhook address the DM: accountId becomes social_account_id, commentId becomes instagram.comment_id, and authorId becomes instagram.author_id. On a Facebook Page the comment id alone is enough. Full reference in Start a conversation.
The category is full of drag-and-drop canvases. This is the layer underneath one.
The webhook hands you the comment text, its author and the post it sits on. What deserves a reply, and what that reply says, is ordinary code in your own stack - a regex, a database lookup, a model call, or an agent deciding for itself.
Instagram and Facebook disagree about who a Page may message and what identifies the recipient. Outstand normalises the surrounding flow: the same endpoint, the same conversation and message objects, the same webhooks. Only the addressing object differs.
A private reply is the first message of a real conversation, not a fire-and-forget send. When the commenter answers, message.received fires and the thread continues through the same API, so your inbox or agent keeps full context.
A billable conversation is counted once per billing period, however many messages it carries. Answering a comment and then holding a twenty-message exchange costs exactly the same as the single reply.
A private reply cannot be followed up until the contact answers. Make answering a single tap.
Comment-to-DM automation answers a public comment in the commenter's direct messages. Someone comments "LINK" on a post and your application sends them the link, a discount code or a sign-up form as a DM, then keeps the thread going when they reply. Meta calls the first message a private reply: a business cannot message someone out of the blue, but it can answer a comment privately. Outstand exposes that as two pieces of API surface - the comment.received webhook and POST /v1/conversations - so the logic in between is yours.
Connect an Instagram Business or Creator account with the instagram_business_manage_messages and instagram_business_manage_comments scopes and "Allow access to messages" enabled, subscribe a webhook endpoint to comment.received and message.received, then call POST /v1/conversations with instagram.comment_id and instagram.author_id taken straight off the webhook payload. The commenter receives your text as a DM. The full walkthrough, with a worked Node.js handler, is in the Comment-to-DM guide.
Yes. Facebook comment to DM sends a private reply from the Page with facebook.comment_id, and the Page has to be connected with the pages_messaging scope. Facebook needs only the comment id, not the author id, because the Page replies to the comment rather than addressing the person directly. Otherwise Instagram comment to DM and Facebook comment to DM are the same call against the same endpoint, and they return the same conversation and message objects.
Not through Outstand, and we would rather say so here than at integration time. Starting a conversation is supported on Instagram and Facebook only; a request against any other connected account is rejected with 400 and the message "Conversations are not supported on <network>". Threads is a near miss worth understanding: Threads replies do arrive on the comment.received webhook, so you can read and act on them, but there is no DM surface to answer them in, and the payload's authorId is null on Threads anyway.
A builder gives you a visual canvas and owns the logic. Outstand gives you a REST endpoint and owns nothing above it. If your keyword matching is a database query, your copy comes from your own CMS, your decisions run through a model, or the whole flow belongs to an agent you are building, then a drag-and-drop flow chart is the wrong shape and you end up fighting it. The trade is real in both directions: there is no dashboard here where a marketer assembles a funnel without you.
They come from Meta, not from us, and they are the same on both networks. One private reply per comment, and a second request for the same comment returns 409. Within seven days of the comment, after which the request returns 422. Text only on that first message, with no media. And the private reply does not open the 24-hour messaging window - the commenter's answer does, so POST /v1/conversations/{id}/messages returns 422 until they reply. Quick replies and buttons exist largely to make that answer one tap.
Billing is per billable conversation, not per message. A conversation counts once in a billing period when at least one message in it was sent through the API and delivered by the platform, so a long back-and-forth costs the same as a single reply. Pay as you go is $19/month and includes 500 billable conversations per period, then $0.025 each. Business includes substantially more at a lower rate. Connected accounts are unlimited on every plan.
Sometimes, and the honest answer matters because gating a reply on it will otherwise silently misfire. The comment.received payload carries an author object with the follow relationship and follower count, but Instagram only reveals that to accounts the person has already messaged, so it is null for most first-time commenters and always null on other networks. Treat null as unknown, never as "not a follower". If the distinction drives your flow, do the check on message.received once they have replied.
No, and that is deliberate. Comments written by the connected account itself, including replies sent through the API, do not fire comment.received. Without that suppression an auto-responder would answer itself in a loop. Comments are not stored by Outstand, so the webhook is the only record we pass on - deduplicate on commentId, since a comment can occasionally be delivered more than once.
$19/month, with 500 billable conversations included. Unlimited connected accounts. See the facts page for current pricing and coverage.