Building a Real-Time Facebook Page Comment Listener Using Webhooks (Node.js + Graph API)
If you’ve tried enabling Facebook Page webhooks in the Meta UI, you probably know the frustration: it looks “subscribed,” but your server…
Building a Real-Time Facebook Page Comment Listener Using Webhooks (Node.js + Graph API)

If you’ve tried enabling Facebook Page webhooks in the Meta UI, you probably know the frustration: it looks “subscribed,” but your server receives absolutely nothing.
The most reliable approach is to:
- Subscribe via the Graph API
- Properly verify your webhook endpoint
- Handle
feedchanges (which include comments) in code
In this article, we’ll build a real-time Facebook Page comment listener using:
- Facebook Graph API (v23.0)
- Webhooks
- Node.js + Express
What We’re Building
Webhook → Your Server → Normalize → Store
When something happens on a Facebook Page, Meta sends a webhook payload like this:
field: "feed"
value.item: "comment" | "post" | "photo" | "video" | ...
value.verb: "add" | "edited" | "remove" | ...
Important
You do not subscribe to “comments” directly.
Comments are delivered under:
field: "feed"
item: "comment"
That’s how Facebook structures Page activity.
Step 0: Get Your App ID and App Secret
- Visit: https://developers.facebook.com/
- Open your app from My Apps.
- Navigate to App Settings → Basic
There you’ll see:
- App ID
- App Secret (click “Show” to reveal it)
Important: Keep Your App Secret Private
Never:
- Commit it to GitHub
- Hardcode it in source code
- Expose it in frontend JavaScript
- Log it in production logs
Store it as an environment variable instead:
export FB_APP_SECRET=your_secret_here
Then use it in Node:
process.env.FB_APP_SECRET
Step 1: Subscribe via Graph API (App-level + Page-level)
There are two subscriptions required:
App-level subscription
Tells Meta where to send Page events for your app.
Page-level subscription
Tells a specific Page to send its events to your app.
Even if the UI looks configured, if you skip the Page-level step you’ll receive nothing!
1A) Create an App Access Token
curl -G "https://graph.facebook.com/v23.0/oauth/access_token" \
--data-urlencode "client_id=APP_ID" \
--data-urlencode "client_secret=APP_SECRET" \
--data-urlencode "grant_type=client_credentials"
Response:
{ "access_token": "APP|..." }
1B) Register the App Webhook Subscription
curl -X POST "https://graph.facebook.com/v23.0/APP_ID/subscriptions" \
-d "object=page" \
-d "callback_url=https://YOUR_DOMAIN/webhooks/facebook" \
-d "verify_token=YOUR_VERIFY_TOKEN" \
-d "fields=feed" \
-d "access_token=APP_ACCESS_TOKEN"
To verify:
curl "https://graph.facebook.com/v23.0/APP_ID/subscriptions?access_token=APP_ACCESS_TOKEN"
We subscribe to:
object=page
fields=feed
Because feed includes comments.
1C) Subscribe a Page to Your App
You need a Page Access Token for the target Page.
curl -X POST "https://graph.facebook.com/v23.0/PAGE_ID/subscribed_apps" \
-d "subscribed_fields=['feed']" \
-d "access_token=PAGE_ACCESS_TOKEN"
Verify:
curl "https://graph.facebook.com/v23.0/PAGE_ID/subscribed_apps?access_token=PAGE_ACCESS_TOKEN"
If the Page is not subscribed, your webhook will never fire.
Step 2: Webhook Verification (GET) — Required for Setup
When configuring webhooks, Meta sends a verification request containing hub.challenge.
Your endpoint must return that challenge.
app.get("/webhooks/facebook", (req, res) => {
const VERIFY_TOKEN = '1234';
const mode = req.query['hub.mode'];
const token = req.query['hub.verify_token'];
const challenge = req.query['hub.challenge'];
if (mode && token === VERIFY_TOKEN) {
res.status(200).send(challenge);
} else {
res.sendStatus(403);
}
}
Step 3: Webhook Receiver (POST) — Parse entry and changes
Facebook batches events under:
body.entry[]
entry.changes[]
change.field
change.value
A production-friendly handler:
app.post("/webhooks/facebook", async (req, res) => {
try {
const entries = req.body?.entry || [];
for (const entry of entries) {
const pageId = entry.id;
const changes = entry.changes || [];
for (const change of changes) {
const field = change.field;
const value = change.value || {};
if (field === "feed") {
await facebookFeed(value, pageId);
}
}
}
return res.status(200).send("EVENT_RECEIVED");
} catch (e) {
// Always respond 200 to prevent retry storms
return res.status(200).send("OK");
}
});
Step 4: Detect Comment Activity
Inside feed, comments are identified by:
const item = value.item; // "post" | "comment" | ...
const verb = value.verb; // "add" | "edited" | "remove"
To handle comments:
if (item === "comment") {
// process comment
}
Build a Human-Friendly Link
For dashboards or alerts:
link = `https://www.facebook.com/${postId}?comment_id=${encodeURIComponent(commentId)}`;
This allows direct navigation to the comment.
Step 5: Enrich Comment Events
Webhook payloads often don’t include media or parent comment info.
To enrich:
GET /{commentId}?fields=attachment,parent
Example:
const url = `https://graph.facebook.com/v23.0/${commentId}`;
const { data } = await axios.get(url, {
params: {
fields: "attachment,parent",
access_token: pageAccessToken,
},
});
Extract useful data:
data.parent.id→ identifies threaded repliesdata.attachment.media.image.srcdata.attachment.media.animated_image.uridata.attachment.media.source
This allows you to:
- Display media previews
- Detect reply threads
- Normalize comment structure
Step 6: Normalize into a Unified Object
Unifying all feed events into a single structure simplifies storage and downstream processing.
Example:
const feedObj = {
accountId: pageId,
channel: "facebook",
sourceField: "feed",
item: item || "post",
verb: verb || null,
uu_id: commentIdOrPostId,
text,
link,
profileName,
profileUrl,
imageUrl,
mediaType,
mention_parent_id,
dateTime: createdAt,
};
Handling Edits & Deletions
Two practical rules:
- If
verb === "edited"and there is no text or image → treat as deleted - If
verb === "remove"anditem === "comment"→ mark deleted
These small details prevent UI inconsistencies later.
Final Thoughts
To reliably build a Facebook comment listener:
- Subscribe the app via
/APP_ID/subscriptions - Subscribe the Page via
/PAGE_ID/subscribed_apps - Listen for
field="feed" - Detect
item="comment" - Enrich with
fields=attachment,parent - Normalize and store
That’s your real-time Facebook comment ingestion pipeline.
What’s Next?
In Part 2, we’ll cover:
- Replying to comments using
POST /{id}/comments
메타데이터
- post_id
- 17dad90e992e
- slug
- building-a-real-time-facebook-page-comment-listener-using-webhooks-node-js-graph-api-17dad90e992e
- url
- https://medium.com/emojot-engineering/building-a-real-time-facebook-page-comment-listener-using-webhooks-node-js-graph-api-17dad90e992e
- canonical_url
- https://medium.com/emojot-engineering/building-a-real-time-facebook-page-comment-listener-using-webhooks-node-js-graph-api-17dad90e992e
- author_url
- https://medium.com/@hasiniwij
- status
- ok
- fetched_at
- 2026-07-19 21:53:32