To integrate Facebook Messenger into your app, you create a Meta app, connect it to a Facebook Page, subscribe a webhook to that Page’s messaging events, and reply through the Send API using a Page access token. That is the whole loop: Meta pushes incoming messages to your server, and your server pushes replies back. Everything else (quick replies, templates, m.me links, app review) is built on top of those four pieces.
pages_messaging, register an HTTPS webhook subscribed to messages and messaging_postbacks, then POST replies to https://graph.facebook.com/v21.0/me/messages. Test with app admins, then submit for App Review before the public can message you.This guide walks through the Messenger Platform as it works today, including one thing that trips up a lot of teams: the old Customer Chat plugin for websites has been retired, so the “embed a chat bubble” approach you may remember no longer works the way it did. If you are also wiring up Facebook sign in for the same app, the Facebook Login integration guide covers that side, and the API authentication guide explains tokens in more depth.
How the Messenger Platform is wired
Messenger integrations always run through a Facebook Page, never through a personal profile. Users message the Page, Meta delivers those events to your webhook, and your app answers as the Page. You need three things registered in the Meta developer dashboard:
- A Meta app of type Business with the Messenger product added.
- A Facebook Page you administer, connected to that app in the Messenger settings panel.
- A Page access token that carries the
pages_messagingpermission. This is the credential you send with every Send API call.
Access tokens come in short lived and long lived flavors. For a production bot, generate a System User token in Business Manager or exchange a user token for a long lived Page token so you are not rotating credentials every hour. Store it as an environment variable, never in client code, because a Page token lets the holder send messages as your business.
Step 1: Create the app and connect the Page
In the developer dashboard, choose Create App, pick the Business use case, and add Messenger from the product list. Under Messenger, Messenger API Settings, click Add or Remove Pages, authorize your Page, and click Generate Token next to it. Copy the token to a safe place.
While you are in that panel, note the App ID and App Secret from App Settings, Basic. The secret is used to validate the X-Hub-Signature-256 header on every webhook delivery, which is how you know a request really came from Meta.
Step 2: Build and verify the webhook
Meta will only talk to a publicly reachable HTTPS endpoint with a valid certificate. During development, a tunnel such as ngrok or Cloudflare Tunnel is the usual answer. The webhook has two jobs: answer a one time GET verification challenge, and accept POST deliveries of events.
Here is a minimal Node and Express webhook that handles verification, checks the signature, and echoes text messages back:
import express from "express";
import crypto from "crypto";
const app = express();
const VERIFY_TOKEN = process.env.VERIFY_TOKEN;
const PAGE_TOKEN = process.env.PAGE_ACCESS_TOKEN;
const APP_SECRET = process.env.APP_SECRET;
const GRAPH = "https://graph.facebook.com/v21.0";
app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } }));
// Webhook verification (one time, when you register the URL)
app.get("/webhook", (req, res) => {
const mode = req.query["hub.mode"];
const token = req.query["hub.verify_token"];
const challenge = req.query["hub.challenge"];
if (mode === "subscribe" && token === VERIFY_TOKEN) return res.status(200).send(challenge);
return res.sendStatus(403);
});
function validSignature(req) {
const header = req.get("x-hub-signature-256") || "";
const expected = "sha256=" + crypto.createHmac("sha256", APP_SECRET).update(req.rawBody).digest("hex");
return header.length === expected.length && crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}
async function send(psid, message) {
await fetch(`${GRAPH}/me/messages?access_token=${PAGE_TOKEN}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ recipient: { id: psid }, messaging_type: "RESPONSE", message })
});
}
// Event delivery
app.post("/webhook", async (req, res) => {
if (!validSignature(req)) return res.sendStatus(401);
res.sendStatus(200); // ack fast, Meta retries slow endpoints
if (req.body.object !== "page") return;
for (const entry of req.body.entry) {
for (const ev of entry.messaging || []) {
const psid = ev.sender.id;
if (ev.message && ev.message.text) {
await send(psid, { text: `You said: ${ev.message.text}` });
} else if (ev.postback) {
await send(psid, { text: `Postback: ${ev.postback.payload}` });
}
}
}
});
app.listen(3000);Back in the dashboard, under Messenger, Webhooks, enter your public URL plus /webhook and the same verify token you set in the environment. Then subscribe the Page to the messages and messaging_postbacks fields. You can also subscribe from the command line:
curl -X POST "https://graph.facebook.com/v21.0/me/subscribed_apps" \
-d "subscribed_fields=messages,messaging_postbacks,messaging_optins" \
-d "access_token=$PAGE_ACCESS_TOKEN"Step 3: Send richer messages
The Send API accepts more than plain text. The message object in the snippet above can carry quick replies, attachments, and structured templates.
Quick replies
Quick replies are tappable chips under a message. Up to 13 are allowed, each with a title and a payload that comes back to your webhook as message.quick_reply.payload:
{
"recipient": { "id": "<PSID>" },
"messaging_type": "RESPONSE",
"message": {
"text": "What do you need help with?",
"quick_replies": [
{ "content_type": "text", "title": "Order status", "payload": "ORDER_STATUS" },
{ "content_type": "text", "title": "Returns", "payload": "RETURNS" },
{ "content_type": "user_phone_number" }
]
}
}Templates
Templates render cards with images, titles and buttons. The generic template is the workhorse; the button template is a text bubble with up to three buttons. Buttons can be postback (sends a payload to you) or web_url (opens a link).
{
"recipient": { "id": "<PSID>" },
"message": {
"attachment": {
"type": "template",
"payload": {
"template_type": "generic",
"elements": [{
"title": "Order #10422",
"subtitle": "Shipped, arriving Thursday",
"image_url": "https://example.com/box.png",
"buttons": [
{ "type": "web_url", "url": "https://example.com/track/10422", "title": "Track" },
{ "type": "postback", "title": "Contact support", "payload": "SUPPORT_10422" }
]
}]
}
}
}
}Message types and the 24 hour window
Every send carries a messaging_type. Use RESPONSE for replies inside the standard 24 hour window after the user last messaged you, UPDATE for proactive follow ups inside that window, and MESSAGE_TAG with an approved tag (for example CONFIRMED_EVENT_UPDATE or POST_PURCHASE_UPDATE) for the narrow set of cases allowed outside it. Marketing outside the window is not one of those cases. The full rules live in the Messenger Platform Send API documentation.
Entry points: m.me links, buttons and the retired Chat Plugin
Once the webhook works, you need a way for people to start a conversation. The simplest is an m.me link, which opens Messenger to your Page on any device:
https://m.me/YOUR_PAGE_USERNAME?ref=pricing_page
<!-- inside your web page or app -->
<a href="https://m.me/YOUR_PAGE_USERNAME?ref=pricing_page">Chat with us on Messenger</a>The ref parameter arrives in your webhook as a referral event (or inside the first postback if you have a Get Started button configured), which lets you tailor the opening message by where the user came from. On mobile apps, open the same URL with the platform’s default link handler and Messenger takes over.
What about the Customer Chat plugin, the embeddable chat window that used to sit in the corner of a website? Meta’s developer documentation for the plugin now states that as of May 9, 2024 the plugin’s functionality is no longer accessible, and guest mode was removed before that. We verified this directly against Meta’s Chat Plugin documentation. If you have an old fb-customerchat snippet in your site, remove it and switch to m.me links, a Send to Messenger button, or a hosted chat widget of your own that hands off to Messenger.
Set up a Get Started button and persistent menu
The Messenger Profile API configures the first touch experience. One call sets the Get Started payload, a greeting, and a persistent menu:
curl -X POST "https://graph.facebook.com/v21.0/me/messenger_profile?access_token=$PAGE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"get_started": { "payload": "GET_STARTED" },
"greeting": [{ "locale": "default", "text": "Hi {{user_first_name}}, ask us anything." }],
"persistent_menu": [{
"locale": "default",
"composer_input_disabled": false,
"call_to_actions": [
{ "type": "postback", "title": "Track an order", "payload": "TRACK" },
{ "type": "web_url", "title": "Visit site", "url": "https://example.com" }
]
}]
}'Permissions, App Review and going live
While the app is in Development mode, only people with a role on the app (admins, developers, testers) can message it. To reach the public you must switch to Live mode, and that requires App Review approval for pages_messaging. Meta also requires Business Verification for most messaging permissions. Prepare a short screencast showing the user flow, a clear description of what the bot does, and a privacy policy URL, because incomplete submissions are the most common reason for rejection.
| Permission or feature | What it unlocks | Needs App Review |
|---|---|---|
| pages_messaging | Send and receive messages as the Page | Yes, plus Business Verification |
| pages_manage_metadata | Subscribe the Page to webhook fields | Yes |
| Human Agent tag | Reply up to 7 days after the last user message when a person answers | Yes, separate feature request |
| Handover Protocol | Pass threads between your bot and the Page Inbox | No, configured in Messenger settings |
Sharing the inbox with humans
Most businesses do not want a bot to own every conversation. The Handover Protocol lets your app be the Primary Receiver and pass a thread to the Page Inbox in Meta Business Suite when a customer asks for a person. Your webhook receives pass_thread_control and take_thread_control events so you know who currently owns the conversation. The reverse works too: an agent in the Business Suite inbox can hand the thread back to your bot.
Troubleshooting
Webhook verification fails with 403
The verify token you typed in the dashboard does not match the one your server compares against, or your server is returning the challenge with extra characters. Echo hub.challenge exactly, as plain text, with a 200 status.
Messages arrive from admins but not from other people
The app is still in Development mode. Until App Review approves pages_messaging and you flip the app to Live, only users with an app role can trigger events.
Send API returns error code 10 or 200
Error 10 usually means you are outside the 24 hour window without a valid tag, or the permission has not been granted. Error 200 typically points to a token that lacks the required scope. Regenerate the Page token after adding permissions, because scopes are baked into the token at generation time.
Signature check keeps failing
You are hashing a re serialized body instead of the raw bytes. Capture the raw request body before JSON parsing, as the Express example above does with the verify hook.
The old chat bubble disappeared from the website
That is the retired Customer Chat plugin. There is no fix beyond replacing it with an m.me link, a Send to Messenger button, or your own widget. See the Messenger basics guide if you need to explain the change to nontechnical stakeholders.
Frequently asked questions
Can I integrate Messenger without a Facebook Page?
No. Every Messenger Platform integration is anchored to a Facebook Page, and all messages are sent and received as that Page. If your product has no Page yet, create one first, then connect it to the app in the Messenger settings panel and generate the Page access token.
Is there a cost to use the Messenger Platform?
The API itself is free to use for standard messaging within the 24 hour window and with approved message tags. Paid options exist for certain marketing messages, and Meta adjusts those programs over time, so check the current Messenger Platform policy pages before planning a campaign that depends on them.
How do I test before App Review approves the app?
Add teammates as testers or developers under App Roles, then have them message the Page from their own Facebook accounts. Events flow to your webhook exactly as they will in production. You can also send test payloads manually with curl using the Page token and a known PSID.
What is a PSID and why does it differ from a user ID?
A Page Scoped ID identifies a user only in the context of one Page. The same person gets a different PSID for each Page they message, and it is not the same as their Facebook Login user ID. If you need to link the two, use the ID matching API that Meta provides for businesses.
Does the Customer Chat plugin still work on websites?
Meta’s own documentation states that the Chat Plugin’s functionality became inaccessible on May 9, 2024. Existing snippets no longer render a working chat window. Replace them with m.me links or a Send to Messenger button, both of which open the conversation in Messenger itself.
The bottom line
A Messenger integration is a small amount of plumbing: a Page token, a verified HTTPS webhook, and a Send API client. Once the echo bot in this article runs, adding quick replies, templates and a persistent menu is mostly a matter of sending different JSON.
Budget real time for App Review and Business Verification, and plan for a human handoff from day one. Those two items, not the code, are what usually decide whether a Messenger project ships on schedule.

