Comment-to-DM troubleshooting

Diagnose missing comment webhooks, failed private replies, duplicate sends, follower checks and messaging-window errors.

Debug the pipeline in order: platform comment, Outstand event, durable job, campaign match, private send, inbound reply and optional public confirmation. A missing DM is not always a messaging API problem.

Keep a sanitized trace with the event timestamp, orgId, network, accountId, platformPostId, postId, commentId, application job/attempt IDs, response status/error and any conversation/message IDs. Never include API keys or access tokens in a support request. Store only the personal data necessary to diagnose the issue.

No comment.received event

Evidence: Create a fresh comment from a different person on a post owned by the connected account. Check your Outstand webhook event subscriptions, endpoint delivery logs, signature failures and persisted inbox records. Comments by the connected account itself are filtered out. Do not use old comments as proof that new webhooks are broken.

Fix: Subscribe to comment.received and verify the endpoint is reachable. For Instagram, confirm comment access and the Business/Creator account setup. For Facebook, check pages_manage_metadata and app feed subscription; a BYOK app also needs its per-app callback configured. For X, verify webhook registration happened before connection, or reconnect afterwards. Follow the network's configuration guide.

If delivery reached your endpoint but no job exists, inspect raw-body verification and persistence errors before changing platform permissions. Do not return 2xx until the event/job has been durably stored.

Native-post comments are ignored

Evidence: Inspect postId, platformPostId, account and network. A native post can have postId: null while platformPostId identifies the platform post correctly. Look for an early if (!data.postId) return or a rule scoped only to Outstand posts.

Fix: Match the authorized connected account plus platformPostId for a native single-post campaign. Do not replace an Outstand post ID in another API path with the native ID. A private DM can work even when the public-reply endpoint cannot target that post. See campaign scoping.

Event exists, but no campaign job sends

Evidence: Read the persisted job's skip reason, rule version, keyword mode, active dates, account/post scope, cooldown key and parentCommentId. Check for null text, case differences, punctuation or overlapping campaigns.

Fix: Correct the rule, not the event. Use whole-token or exact-phrase matching deliberately. Treat top-level-only as your campaign setting: Meta's current Facebook documentation includes comments on comments. Do not widen a single-post campaign to all posts just because the post ID is missing.

400, 401 or 403 on the send

Evidence: Record the sanitized response and request field names. Confirm the API key is authorized for conversations:send and for the account's organization. A webhook organization ID is not automatically your application's tenant ID.

Fix: For Instagram, send instagram.comment_id and instagram.author_id; Facebook uses only facebook.comment_id; X requires exactly one target field. Use the comment event's accountId as social_account_id. Keep credentials server-side and resolve them through your tenant/account authorization mapping. Unsupported networks need a different workflow, not a new retry policy.

404: account or comment not found

Evidence: Compare the event's account/comment pair to the send request, including organization mapping. Verify that the comment still exists and is visible to the connected account. Inspect the actual error because account lookup and platform comment lookup are different stages.

Fix: Correct the IDs or authorization mapping. Do not use a post ID where a comment ID is required, or a Facebook comment author ID as a Messenger recipient ID. A deleted, inaccessible or wrong-account comment needs investigation rather than repeated unchanged requests.

409: already contacted

Evidence: Inspect your durable comment reservation and any prior send attempts. Check for a private reply sent by another campaign, another integration or a previous attempt whose response was lost.

Fix: Stop resending and record already_contacted. This is not proof that your current campaign text was delivered, so do not automatically mark a resource fulfilled or post "Sent you the guide". Reconcile any known conversation/message IDs. Keep the duplicate guard in place on all networks; X can send duplicate DMs instead of returning this protection.

422: private reply or follow-up rejected

Evidence: Read the error and identify which request failed: POST /v1/conversations or /v1/conversations/{id}/messages. Check the actual comment age, the latest inbound timestamp and, for X, recipient eligibility.

Fix: An expired seven-day Meta private reply cannot be fixed by replay. Instagram Live private replies are only allowed while live. For a Meta follow-up, wait for a real inbound response and send within its 24-hour window. The first private reply, a profile follow and a URL click do not open that window. For X recipient restrictions, do not keep sending unchanged requests.

502, timeout or lost response

Evidence: Preserve the error text, attempt times and whether the request reached the send stage. Check conversation messages, message.sent events and the platform inbox. A Facebook error can explicitly say the message was sent but no recipient was returned, preventing local conversation storage.

Fix: Do not blanket-retry 502. Permissions and token errors require reconnecting or app configuration repair. A confirmed transient pre-send rejection can enter bounded backoff. If the platform may have accepted the send, move the job to unknown_outcome and reconcile or review manually. No local message record is not proof of no send. See durable job recovery.

Private reply is sent, but the person cannot find it

Evidence: Separate a 201 response or message.sent event from read evidence. Confirm the correct account and recipient, then have the person check Requests and the inbox in Instagram or Messenger. Compare message IDs where available.

Fix: Treat sent as accepted by the platform, not seen by the person. Do not send another initial private reply as a visibility test. Public confirmation, when supported, can suggest checking message requests, but should only follow a confirmed send.

Duplicate DMs or repeated resource releases

Evidence: Compare comment keys and message IDs. Determine whether duplicates share one comment, come from several comments by the same person, or follow a worker lease expiry/timeout. Check multiple organization connections or overlapping campaign owners.

Fix: Use an atomic unique comment reservation, not an in-memory set. Use a separate per-person cooldown for multiple comments. Keep ambiguous jobs out of automatic replay. Deduplicate inbound message.received and atomically reserve resource fulfillment, so repeated taps do not release again. Coordinate ownership if several organization connections target the same platform account.

Follow-to-unlock says someone is not following

Evidence: Inspect author?.isFollower on the comment and sender?.isFollower on the later inbound message. Distinguish literal false from a null profile or null field. Check whether your code uses if (!isFollower), which merges false and unknown.

Fix: Use === true and === false. Most first-time commenters have no profile enrichment until they message the account. After a quick reply/postback, use the new inbound sender snapshot. For unknown state, offer assistance, a bounded user-requested recheck or a documented fail-open policy. Never claim unknown proves a non-follow. See Instagram follow-to-unlock.

Button opens a profile, but no check runs

Evidence: Inspect whether the button is url or postback, and whether an actual message.received event carries your expected interaction.id. Check app messaging subscriptions separately from comment subscriptions. Quick replies are not displayed on Instagram desktop.

Fix: Use a quick reply or postback button for a check, and offer a typed fallback such as CHECK. A URL click does not generate an inbound reply, verify a follow or open the messaging window. For BYOK, ensure Instagram messages and messaging_postbacks subscriptions are configured as described in messaging setup.

Facebook buttons or follower gating do not work

Evidence: Inspect the request schema and normalized profile. Outstand's Facebook start-conversation options take comment_id; they do not expose Instagram quick replies, buttons or a follower boolean for this flow.

Fix: Send the first Facebook private reply as plain content. This is an Outstand implementation limit, not a universal Messenger text-only rule. Do not reuse Instagram follow-to-unlock logic for Facebook or interpret its null profile fields as non-follower evidence. Meta also disallows private replies to another Facebook Page, and Standard Access tests only establish access for people with an app role.

DM succeeded, public confirmation failed

Evidence: Check the separate DM and public-comment job states. Verify the public endpoint's Outstand post ID, supported account/network, platform_post_id, parent_comment_id and interactions:reply access. A native-post event may have no usable Outstand postId.

Fix: Repair or omit only the confirmation job. Never replay the private DM to recover a public-comment failure. Keep public and private capabilities separate, and reconcile an uncertain public-send outcome before posting again.

Escalation checklist

Send support a sanitized event, account/network and organization IDs, UTC timestamps, response status/error, application job history and any Outstand/platform message IDs. State whether the post was native, whether a real inbound reply occurred, and what the platform inbox shows. Include your last confirmed pipeline stage and expected result. Redact credentials and unrelated personal information.