PostZen

Comment automations

Turn Instagram comments and story replies into automatic DMs. Keyword matching, private replies, optional public replies, and a log of every trigger.

A comment automation watches a connected Instagram account. When someone comments on a post, or replies to a story, and the text matches the automation's keywords, PostZen sends that person a direct message and can post a public reply under their comment. Each trigger is recorded so you can see who got what and why.

This is the "comment the word LINK and I'll DM you" pattern, run from your API key instead of a chatbot subscription. It is built on Meta's private reply mechanism, which lets a business message a commenter once without the commenter having messaged first.

Automations run on Instagram only. The account needs the comment and messaging permissions, which every account connected since 2026-09-12 grants. If an older account returns 403 platformCapabilityMissing, reconnect it from the Connections page.

How it works

  1. You create an automation for an account with POST /v1/comment-automations. It can be scoped to one post, or left account-wide.
  2. Meta sends PostZen a webhook the moment a comment or story reply lands on that account. Nothing polls.
  3. PostZen picks the automation that applies. A per-post automation always wins on its own post. Otherwise account-wide automations are tried oldest first, and the first one whose keywords match fires.
  4. The DM goes out, immediately or after the delay you set. The public reply, if configured, follows the DM.
  5. A log row records the outcome, and the automation's stats counters move.

Two Meta rules shape everything else on this page. A commenter can receive one private reply per comment, ever, and it has to be sent within seven days of the comment. Because a second reply to the same comment is refused by Meta, PostZen can retry a failed send without any risk of messaging someone twice.

Create an automation

curl -X POST https://api.postzen.dev/v1/comment-automations \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "'"$ACCOUNT_ID"'",
    "name": "Launch link",
    "platformPostId": "17900000000000001",
    "keywords": ["link", "guide"],
    "matchMode": "word",
    "typoTolerance": true,
    "dmMessage": "Here is the guide you asked for 👇",
    "buttons": [{ "type": "url", "title": "Open the guide", "url": "https://example.com/guide" }],
    "commentReply": "Sent you a DM!",
    "commentReplyDelaySeconds": 30
  }'

The response is { message, automation }. The automation object carries every setting back, plus stats, createdAt, and updatedAt.

Scope

FieldEffect
platformPostIdThe Instagram media id. Only comments on that post trigger. For story_reply, the story id.
postIdA PostZen post id instead. PostZen resolves it to the Instagram media id when the post publishes, so you can attach an automation before the post goes live.
NeitherAccount-wide. Every post on the account is covered.

Only one active per-post automation may exist for a given post. Creating or activating a second returns 409 duplicatePostAutomation. Account-wide automations stack without limit, and they are skipped on any post that has its own automation.

Matching

FieldBehavior
keywordsUp to 50. Empty means every comment triggers.
matchModecontains (default) matches anywhere in the text. exact requires the whole comment to equal a keyword. word matches whole words only.
excludeKeywordsChecked first, with the same mode. A hit here vetoes the trigger.
typoToleranceWith word mode, a keyword of 4 to 7 characters tolerates one typo and a keyword of 8 or more tolerates two. Shorter keywords never fuzz.

Matching is case-insensitive and accent-insensitive, so Café matches cafe.

The message

FieldRules
dmMessageRequired. Up to 1,000 characters, or 640 when buttons are set.
dmMessageVariationsUp to 5 alternates. One of the message and its variations is picked at random for each send.
buttonsUp to 3 link buttons, each { type: "url", title, url }. Meta documents private replies as text only, and button templates work in practice. If Meta rejects the template for a particular send, PostZen resends as text with the links appended and marks the log buttonsDropped: true.
commentReplyOptional public reply posted under the comment. Not used for story replies.
commentReplyVariationsUp to 5 alternates, rotated independently of the DM.

Timing

dmDelaySeconds defers the send by up to 24 hours. The match and the dedupe still happen the instant the comment arrives, so a delayed automation cannot be triggered twice by the same person while it waits. commentReplyDelaySeconds defers the public reply, which never posts before the DM. Both default to zero.

Who gets messaged

  • The account's own comments never trigger.
  • A person receives one DM per automation. A second matching comment from the same person is logged as skipped with skipReason: "already_sent_to_contact".
  • Story replies are matched the same way against automations with trigger: "story_reply". The DM is sent as a normal message, which Meta allows because the reply opened a 24-hour window.

Manage automations

# List, optionally filtered by profile or account
curl "https://api.postzen.dev/v1/comment-automations?accountId=$ACCOUNT_ID" \
  -H "Authorization: Bearer $POSTZEN_API_KEY"

# Get one, with its 20 most recent logs inline
curl "https://api.postzen.dev/v1/comment-automations/$AUTOMATION_ID" \
  -H "Authorization: Bearer $POSTZEN_API_KEY"

# Pause it
curl -X PATCH "https://api.postzen.dev/v1/comment-automations/$AUTOMATION_ID" \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "isActive": false }'

# Delete it and its logs
curl -X DELETE "https://api.postzen.dev/v1/comment-automations/$AUTOMATION_ID" \
  -H "Authorization: Bearer $POSTZEN_API_KEY"

PATCH is a partial update. Send only the fields you want to change. Pass [] to clear keywords, excludeKeywords, buttons, or a variations list. The account cannot be changed after creation.

Logs

curl "https://api.postzen.dev/v1/comment-automations/$AUTOMATION_ID/logs?status=failed&limit=50&skip=0" \
  -H "Authorization: Bearer $POSTZEN_API_KEY"

The response is { logs, pagination } with pagination.total, limit, skip, and hasMore. Each log carries the comment id, the commenter's id and username, the comment text, and the outcome.

statusMeaning
pendingMatched and waiting for the send, or for the pool to pick it up. nextDueAt says when.
sentThe DM was delivered. sentMessageId is Meta's id, chosenDmText is the variation that went out.
failedMeta refused, or retries ran out. error carries Meta's wording. The most common causes are the seven-day window and a private reply that was already sent to that comment from elsewhere.
skippedMatched but deliberately not sent. skipReason explains, for example already_sent_to_contact or automation_inactive.

The public reply has its own commentReplyStatus and commentReplyError. A failed public reply never changes the DM's status or the automation's counters.

stats.totalTriggered counts every match, including skips. totalSent and totalFailed count DM outcomes only.

Limits

LimitValue
Automations per user100
Active per-post automations per post1
Keywords, exclude keywords50 each
Variations5 for the DM, 5 for the reply
Buttons3
Delays0 to 86,400 seconds
Private replies per account750 per hour. Beyond that PostZen holds sends and releases them as the window frees, rather than letting Meta reject them.
Create, update, delete calls60, 120, and 60 per hour

Not supported yet

Facebook Page comments, product card templates, follower-only audiences and the follow gate, matching keywords inside inbound DMs, and click tracking are accepted by Zernio's API but return 400 unsupported here. Send linkTracking: false or omit it.

On this page