“Follow us, then tap to get the link” looks like one automation. Through the Instagram API, it is two different decisions separated by a user interaction.
First, decide whether the comment qualifies for a private reply. Then check whether Instagram reports a follow. For first-time commenters, that information is usually unavailable: a comment does not grant profile access.
Outstand is not a background follower monitor. Start with a comment, ask the person to respond in DMs, then check the fresh sender information on that inbound event. Here is the state machine, including unknown results.
What Instagram actually lets you check
Meta's Instagram User Profile API includes two relationship fields:
is_user_follow_business: whether the Instagram user follows the professional account.is_business_follow_user: whether the professional account follows that user.
Outstand maps those to isFollower and isFollowedByAccount. For unlocking a link, you want isFollower. The reverse relationship says nothing about whether the person completed your requested action.
Meta requires user consent to access the profile. Its documentation explicitly distinguishes sending a message from commenting: a person who has only commented has not established that profile access. Consequently, a new comment's author can be null even if the person already follows you.
In Outstand's implementation, the Instagram profile lookup is skipped when the contact has never messaged the account. For comments from someone with a prior inbound conversation, Outstand can attempt a lookup and include author. That is useful for a fast path, not a reason to assume every comment carries a follower flag.
Null is not “not following”
Use three states, not two:
Value | Interpretation | Appropriate next step |
|---|---|---|
| Instagram reports a follow | Deliver the link if the campaign permits |
| Instagram reports no follow | Explain the requirement and offer another check |
Missing profile or null flag | Follow status is unknown | Explain that verification was unavailable and use your fallback |
Avoid if (!data.sender?.isFollower) as a business rule. It collapses a known non-follower and an unavailable lookup into the same outcome. That can accuse an existing follower of doing something wrong when the API simply did not answer.
The distinction survives Outstand's normalization: missing or invalid relationship values become null rather than false. An unsuccessful profile lookup can also make the entire sender null. Your copy should preserve that uncertainty.
Stage one: answer the comment without pretending to know
Use the comment-to-DM API guide to configure the connected Instagram Business or Creator account and messaging access. Subscribe to comment.received and message.received. Keep existing required scopes when requesting messaging permissions, because an explicit scopes list replaces the defaults.
For BYOK, subscribe your Meta app to comments, messages and messaging_postbacks on its verified callback. Both app-level and account-level subscriptions are required; Outstand configures the account-level fields when you connect with the appropriate granted permissions. Postback buttons need messaging_postbacks, not just messages. Your Outstand webhook subscription does not replace these upstream Meta subscriptions.
On a verified Instagram comment event, filter for the campaign's post and keyword. If data.author?.isFollower === true, you can deliver the link immediately in the private reply. Otherwise, send an invitation to follow and check. Do not describe that branch as proof the commenter is not following.
Send this body to POST https://api.outstand.so/v1/conversations, replacing the example IDs with accountId, commentId and authorId from the event:
1{
2 "social_account_id": "x8Kp2",
3 "content": "Follow @yourbrand for the guide, then tap Check follow. Already following? Tap to check. If the option is missing, reply CHECK.",
4 "instagram": {
5 "comment_id": "17865799348089039",
6 "author_id": "17841400000000000",
7 "quick_replies": [
8 { "title": "Check follow", "id": "FOLLOW_CHECK_V1" }
9 ]
10 }
11}Use your server-side API key as a Bearer token and send JSON. Persist the returned conversation ID against the campaign and comment. Do not expose credentials to the browser or accept an arbitrary conversation ID from a public client.
The private reply is not an open-ended messaging permission. Meta's private-reply rules allow one reply per comment within seven days for post and Reel comments. You cannot follow it with a reminder just because the person has not checked yet. The initial outbound message does not open the standard 24-hour window.
Stage two: check the sender after the tap
When the user taps the quick reply, Outstand emits message.received with interaction.id: "FOLLOW_CHECK_V1". A postback button can provide the same application action, with interaction.type: "button" instead of quick_reply.
Crucially, Outstand looks up the participant profile on each newly ingested inbound message. The sender on this event is based on that lookup, rather than a follow flag saved when the comment first arrived. A follow made between the comment and the tap can therefore be reflected in the result.
This is a fresh lookup attempt, not a guarantee that Meta has already reflected every recent relationship change. Read the returned value. The tap itself is a request to check, not proof of a follow.
The following JavaScript selects the response body for a verified, campaign-matched inbound event. It deliberately handles all three states:
1function followResponse(sender) {
2 if (sender?.isFollower === true) {
3 return {
4 content: 'Thanks for following! Here is the guide: https://example.com/guide',
5 };
6 }
7
8 const content = sender?.isFollower === false
9 ? 'The latest check shows no follow yet. Follow @yourbrand, then check again.'
10 : 'We could not verify your follow right now. Try once more, or reply HELP.';
11
12 return {
13 content,
14 instagram: {
15 quick_replies: [{ title: 'Check again', id: 'FOLLOW_CHECK_V1' }],
16 },
17 };
18}Send the selected body to POST https://api.outstand.so/v1/conversations/{conversationId}/messages, using the conversation ID from the inbound event. Check the HTTP result and returned message status: an accepted, queued normal send is not yet a successful send and can fail later. Store its message ID and keep it in accepted state while pending. Reconcile message.sent and message.failed events against that ID, moving to sent or recording the failure. Do not mark fulfillment complete merely because the HTTP request succeeded; sent also does not mean read.
The inbound tap opens the standard 24-hour messaging window. That is why stage two uses the normal message endpoint rather than trying to send a second private reply to the original comment.
Store a small state machine, not a follower guess
The flow needs two stages and a few terminal outcomes:
- Comment stage: validate the campaign and send either the link or the check prompt. Store the conversation mapping.
- Verification stage: accept an expected inbound check, evaluate its fresh sender profile and either deliver, offer a bounded retry or move to fallback.
Within stage two, keep statuses such as awaiting_check, verification_unavailable, accepted, sent, failed and manual_help. Subscribe to message.sent and message.failed for reconciliation. Store attempt count, latest check time and message identity. A profile value is an observation at a particular time, not permanent proof of a relationship.
Serialize work per conversation and campaign. Deduplicate webhook deliveries and make fulfillment idempotent. A repeated button tap might be a new inbound message; it should not trigger another coupon allocation or create another lead record after fulfillment.
Only accept FOLLOW_CHECK_V1 when the conversation is enrolled in that campaign. For typed CHECK, require the same pending state. A message that happens to contain the word in an unrelated support thread should not start a promotion.
Do not build a waiting state called “follow webhook received.” This flow does not rely on a follow-change webhook, and following alone does not automatically confirm the action or trigger link delivery. Ask the person to tap or reply again when another lookup is needed.
Choose a fallback before you launch
A strict gate can withhold the link unless the value is explicitly true, but unknown results need an honest exit. After a bounded number of user-requested checks, offer human help rather than an endless “follow first” loop. A softer campaign can send the link when verification remains unavailable and record that follow status was unconfirmed, separately from the message's send status.
Those are product policies, not alternative meanings of null. Decide which matters more for this campaign: strict verification or avoiding a blocked reader. Explain the requirement in the original public post so the DM does not introduce a surprise condition.
Also build a text path. Meta documents that quick replies are unavailable on desktop. Private replies to non-followers normally land in Requests, while followers receive them in the inbox. Test both locations and do not promise identical control rendering across clients or account states. A typed CHECK can produce another inbound event when a button is unavailable.
Track verification outcomes separately from delivery outcomes. “Unknown” is not a failed follow, and “link sent” is not proof the person opened it or will remain a follower. Monitor rejection rates, unavailable profiles and fallback usage before expanding the campaign.
Build on the correct boundary
This is an Instagram-specific relationship check, not a generic rule for every messaging platform. It also does not turn the 24-hour window into a license for later automated promotion. Process the reply promptly and respect send errors and window expiration.
For the surrounding workflow, see the comment-to-DM solution, the main Instagram comment-to-DM article and the follow-to-unlock implementation guide.
The reliable promise is narrow: when someone asks you to check, use the latest available sender profile before deciding whether to send the link. Do not replace that check with a button label, an old comment snapshot or an assumption that missing data means no.