Listen for Comments
Comments reach your integration through the comment.received webhook event. There is no polling endpoint and no need for one — the event fires for every new comment on a connected Instagram or Facebook Page post, whether or not one of your automations answers it.
Subscribe
Add comment.received to a webhook endpoint's subscribed_events:
curl -X POST "https://api.sendseven.com/api/v1/webhooks" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"name": "Comment listener",
"url": "https://example.com/hooks/sendseven",
"subscribed_events": ["comment.received"]
}'
See Webhook Setup for endpoint management, retries and signature verification.
The event
comment.received uses the standard webhook envelope. Comments are not conversations — a comment has no conversation and no message — so data carries the post and the comment directly.
The full payload example and field-by-field reference live in Webhook Events → Comment Events. These are the fields you make decisions on:
| Field | Why it matters |
|---|---|
event_id | Meta's own comment id. Use it to de-duplicate — see below. |
data.comment.external_id | The same comment id, and the value you pass to the private-reply endpoint. |
data.comment.private_reply_window_expires_at | When the 7-day private-reply window closes. null means the platform gave no reliable comment timestamp. |
data.auto_replied | true when a SendSeven rule or flow already answered this comment. Skip it. |
data.channel_id | The channel the post belongs to. Matches channel_id on message events. |
author.username is null on FacebookInstagram supplies the commenter's handle. Facebook does not expose handles at all — data.comment.author.name is a display name, not something you can address or link to. Do not build a mention out of it.
De-duplicate on event_id
Meta redelivers webhooks. event_id is set to Meta's comment id, which is stable, so the same comment always produces the same event_id. Store it and ignore repeats.
SendSeven already suppresses redeliveries it recognises, but your endpoint must still be idempotent — an HTTP retry after your own timeout will legitimately deliver the same event twice.
Scope events to specific channels
Comment events respect a webhook endpoint's channel source filter. With source_filter_mode: "selected", comment.received is matched on data.channel_id, exactly like message events.
What fires and what does not
| Event on the platform | comment.received |
|---|---|
| New top-level comment on your post | Yes |
| Reply to another comment on your post | Yes — is_reply: true, with parent_external_comment_id set |
| Comment edited | No |
| Comment deleted or hidden | No |
| Your own account's comment (including your public replies) | Not delivered as an actionable comment — Meta echoes your own comments back, and answering them would DM yourself |
Handling an event
The decision an integration actually has to make is short: has it already been answered, is the window still open, and do I want to answer it.
from datetime import datetime, timezone
def handle_comment_received(event):
# 1. Idempotency — Meta redelivers, and so do our retries.
if already_processed(event["event_id"]):
return
data = event["data"]
comment = data["comment"]
# 2. A SendSeven rule or flow may already own this comment.
# Meta permits exactly one private reply per comment, ever.
if data.get("auto_replied"):
record_seen(event["event_id"])
return
# 3. The 7-day window. `null` means we cannot prove it is open.
expires_at = comment.get("private_reply_window_expires_at")
if not expires_at:
return
if datetime.fromisoformat(expires_at.replace("Z", "+00:00")) <= datetime.now(timezone.utc):
return
# 4. Your own matching logic.
if "price" in (comment.get("text") or "").lower():
send_private_reply(
comment_id=comment["external_id"],
channel_id=data["channel_id"],
text="Hi! Our current pricing is at example.com/pricing",
)
record_seen(event["event_id"])
auto_replied before you answerIf you run your own tooling alongside SendSeven auto-reply rules, auto_replied is what stops you racing them for the single private reply Meta allows. The loser of that race gets a permanent 409.
Reacting inside SendSeven instead
You do not have to receive the webhook at all to act on comments:
- Auto-reply rules match keywords and send a private reply or start a flow, with no code.
- The
comment_receivedflow trigger starts a flow directly from a matching comment.
The webhook and the automations are independent: comment.received fires either way, so you can log and analyse every comment while SendSeven handles the answering.
Next steps
- Send private replies — answer a comment from your own code
- Auto-reply rules — answer without code
- Webhook Events → Comment Events — the complete payload reference