# Facebook Messenger Channel — Configuration Guide

Integrate GoClaw with Facebook Messenger so people can talk to an agent through a Facebook Page.

This guide follows the Meta App Dashboard as it is in 2026, where an app is built around **use cases**. If your dashboard still shows the older "Products → Messenger → Settings" layout, the same settings are there under those names.

## Prerequisites

- A Meta developer account ([developers.facebook.com](https://developers.facebook.com))
- A Facebook Page, and full control of it (the `MESSAGING` and `MODERATE` tasks on the Page)
- A public HTTPS endpoint for GoClaw with a valid certificate — a real domain or a tunnel such as ngrok. Meta does not accept self-signed certificates.

## 1. Create the app

1. Go to [developers.facebook.com/apps](https://developers.facebook.com/apps) → **Create app**.
2. Name the app and give a contact e-mail.
3. Under **Use cases**, choose **Engage with customers on Messenger from Meta**. This adds the Messenger permissions (`pages_messaging`, `pages_manage_metadata`, `pages_show_list`) to the app.
4. Connect a business portfolio if you are asked to, then **Create app**.
5. In the dashboard, open **Use cases** → **Engage with customers on Messenger from Meta** → **Customize**. The page that opens is **Messenger API Settings**; steps 2 and 5 happen there.

## 2. Connect the Page and generate a Page access token

1. In **Messenger API Settings** → **Generate access tokens**, click **Connect** (or **Add or remove Pages**).
2. Log in, select the Page, grant the permissions, and **Save**.
3. Next to the Page, click **Generate** and copy the token. Meta shows it only once.
4. Put it in `page_token` (step 4).

A token generated here does not expire on a timer. It stops working when the person who generated it loses their role on the Page, changes their password, or removes the app — generate a new one then.

## 3. Copy the app secret and choose a verify token

1. **App settings** → **Basic** → **App secret** → **Show**, and copy it into `app_secret`. GoClaw uses it twice: to check the `X-Hub-Signature-256` signature on every webhook delivery, and to add `appsecret_proof` to every Send API call. Without it, anyone who finds the webhook URL can post fake messages, so GoClaw refuses Page comments and Lead Ads submissions until it is set (Messenger messages still pass pairing or the allowlist), and says so once at start-up.
2. Choose any random string as the verify token, for example `my-secret-verify-token-2026`, and put it in `verify_token`. You enter the same string in the dashboard in step 5.

If **App settings** → **Advanced** → **Require App Secret** is on, `app_secret` is required: Meta refuses Send API calls that do not carry `appsecret_proof`.

## 4. Configure `config.yaml`

```yaml
channels:
  facebook:
    enabled: true
    page_id: ""                          # optional Page ID (auto-detected from /me if omitted)
    page_token: "${FB_PAGE_TOKEN}"       # Page access token (step 2)
    verify_token: "${FB_VERIFY_TOKEN}"   # the string you chose (step 3)
    app_secret: "${FB_APP_SECRET}"       # App secret (step 3)
    comments_enabled: false              # set true to receive Page post comments (webhook field: feed)
    auto_reply_comments: false           # set true to post the agent's reply as a public comment reply
```

Keep the values in the environment or the secret vault (`${secret:KEY}`) rather than in the file.

## 5. Start GoClaw and register the webhook

Start the gateway:

```bash
goclaw          # no subcommand — running goclaw is what starts the gateway
```

```
[facebook] Channel started (webhook: /hook/facebook, comments_enabled: false)
```

The callback URL is `https://<your-domain>/hook/facebook`. If `gateway.webhook_secret_slug` is set, it is `https://<your-domain>/hook/<slug>/facebook` — use the path the log line prints.

In **Messenger API Settings** → **Configure webhooks**:

1. **Callback URL**: the URL above.
2. **Verify token**: the string from step 3.
3. **Verify and save**. Meta calls the URL; the log shows `[facebook] Webhook verified successfully`.
4. Subscribe to the webhook fields you use:
   - **`messages`** — inbound Messenger chat messages, attachments, and quick-reply payloads.
   - **`messaging_postbacks`** — button taps on templates, Get Started, Ice Breakers, and Persistent Menu items.
   - **`messaging_referrals`** — Click-to-Messenger ad referrals and `m.me?ref=...` entry links.
   - **`feed`** (under Page subscriptions) — real-time Page post comments when `comments_enabled: true`.
   - **`leadgen`** (under Page subscriptions) — Facebook Lead Ad form submissions.
   - Echoes of the Page's own messages (`message_echoes`) and comments posted by the Page itself are ignored automatically.

Then, back in **Generate access tokens**, click **Add subscriptions** next to the Page and tick the same fields (`messages`, `messaging_postbacks`, `messaging_referrals`, and optionally `feed` / `leadgen`). **This step is easy to miss and nothing arrives without it**: the webhook settings say what the app wants to hear about, and this says which Page it listens to. The same can be done with the Page token:

```bash
curl -X POST "https://graph.facebook.com/v25.0/<PAGE_ID>/subscribed_apps?subscribed_fields=messages,messaging_postbacks,messaging_referrals,feed&access_token=<PAGE_TOKEN>"
curl "https://graph.facebook.com/v25.0/<PAGE_ID>/subscribed_apps?access_token=<PAGE_TOKEN>"   # check
```

> **With ngrok:** run `ngrok http 8080` and use `https://xxxx.ngrok-free.app/hook/facebook` as the callback URL. A free ngrok URL changes on every restart, and the webhook has to be registered again.

## 6. Who can talk to the bot: development mode and App Review

While the app is in **development mode**, only people with a role on the app (administrators, developers, testers — **App roles** → **Roles**) get replies. Everyone else's messages never reach the webhook.

To answer the public:

1. Request **Advanced access** for `pages_messaging` (and `pages_manage_metadata`) under **App Review** → **Permissions and features**, with a screencast of the bot answering.
2. If you use the **`facebook_page`** tool for Page posts, comments, or analytics, also grant these permissions to the Page token:
   - `pages_manage_posts` — publish, schedule, update, and delete Page posts (`create_post`, `update_post`, `delete_post`).
   - `pages_read_engagement` & `pages_manage_engagement` — read comments, post public comment replies, send Comment-to-Messenger Private Replies (`private_reply`), like objects, and hide/delete comments (`moderate_comment`).
   - `read_insights` — read Page and post analytics (`page_insights`, `post_insights`: Views, Reach, Engagements, Reactions).
3. Complete **business verification** if Meta asks for it.
4. Switch the app to **Live**.

## 7. Bind an agent (optional)

```yaml
bindings:
  - agent_id: "support"
    match:
      channel: "facebook"
```

## 8. The `facebook_page` Agent Tool (Posts, Comments, Analytics & Flows)

When `channels.facebook.page_token` is configured, agents can call the `facebook_page` tool:

- **Publishing & Scheduling Posts**: `page_info`, `list_posts` (published or `scheduled: true`), `get_post`, `create_post` (text, link, remote `image_url`, or workspace `image_path`, with optional `scheduled_publish_time`), `update_post`, `delete_post`.
- **Comments & Comment-to-Messenger DM**: `list_comments`, `reply_comment` (public reply under a comment), `private_reply` (sends a direct Messenger DM to the commenter via `recipient.comment_id` within 7 days of their comment), `moderate_comment` (hide, unhide, or delete), `like_object`.
- **Page & Post Insights**: `page_insights` (`page_media_view`, `page_impressions_unique`, `page_post_engagements`, `page_daily_follows_unique`, `page_video_views` by `day`, `week`, or `days_28`) and `post_insights` (`post_media_view`, `post_impressions_unique`, `post_clicks`, `post_reactions_by_type_total`).
- **Interactive Messenger Flows**: `send_quick_replies` (up to 13 quick-reply chips), `send_template` (`button` template or `generic` carousel of up to 10 cards), `get_messenger_profile`, and `set_messenger_profile` (Welcome Greeting, Get Started payload, Ice Breakers FAQ starters, and Persistent Menu).

Administrative mutations (`create_post`, `update_post`, `delete_post`, `moderate_comment`, `page_insights`, `post_insights`, `set_messenger_profile`) require an admin or SOP context so external chat users cannot trigger them via prompt injection.

## How GoClaw talks to Messenger

- Replies use the Send API (`/me/messages`, Graph API v25.0) with `messaging_type: RESPONSE`. Meta allows that within **24 hours** of the person's last message; after that a reply is refused, and GoClaw does not send message tags.
- For Page comments (`comments_enabled: true`), the agent can reply publicly (`reply_comment` or `auto_reply_comments: true`) and send a private DM (`private_reply`) within 7 days of the comment.
- Sender display names (`first_name`, `last_name`) are resolved from the Graph API and cached in memory.
- A reply longer than 2,000 characters is sent as several messages, split between lines or at a space.
- Images, audio, video and files sent to the Page are downloaded (from Facebook's CDN only, up to 50 MB) and handed to the agent.
- Files the agent produces are sent as Messenger attachments: an image, video or audio attachment by the file's type, anything else as a file.
- While the agent works, the person sees the typing indicator; it is renewed every 15 seconds.
- Repeated deliveries of the same message, postback, or comment are processed once.

## Comments, leads and `dm_policy`

`channels.dm_policy` and pairing are for people who write to the Page in Messenger. A comment under a Page post and a Lead Ads submission are events from the Page, not a person asking to talk to the agent, so they do not go through pairing or the allowlist:

| Event | Reaches the agent when | Runs as |
|---|---|---|
| Messenger message, postback, referral | `dm_policy` lets the sender in (see [Pairing](pairing.md)) | the paired user, with their role |
| Page post comment (`feed`) | `app_secret` is set and `comments_enabled: true`, under any `dm_policy` | an ordinary `user`, with no paired identity |
| Lead Ads submission (`leadgen`) | `app_secret` is set and the Page is subscribed to `leadgen`, under any `dm_policy` | an ordinary `user`, with no paired identity |

- A comment or lead never creates a pairing request, and the access gate's text ("Pairing Required" with a code, or "You are not authorized to use this bot.") is never posted as a comment reply.
- A comment runs as an ordinary user even when the commenter's Facebook ID belongs to a paired admin: the comment is public, and anyone can write one.
- With `comments_enabled: false`, comments are not handled at all, whatever `dm_policy` is.
- Without `app_secret`, comments and leads are not handled at all: they skip the access gate, and only the signature proves that Facebook sent them.
- Instructions and hooks limited to `roles: [user]` apply to comments and leads as they do to paired users. To write an instruction for comments or leads alone, give it `channels: [facebook]` and a `message_match` on `Facebook Page Comment` or `Facebook Lead Ad Submission`, the first line of what the agent receives for each.

## Testing

From an account with a role on the app (step 6), send a message to the Page. The log shows `[facebook] Message from <PSID>: …`.

With the default `channels.dm_policy: pairing`, the first reply to a Messenger message is **Pairing Required** with an 8-character code and the sender's Facebook ID (their page-scoped ID, PSID). Approve the code on the Web UI's **Pairing** tab — see [Pairing](pairing.md) — and send the message again; this time the agent's reply arrives in Messenger.

## Troubleshooting

| Symptom | Cause and fix |
|---|---|
| "The URL couldn't be validated" when saving the webhook | GoClaw is not reachable over HTTPS at that URL, the path is wrong (check the slug), or `verify_token` differs. The log says `Webhook verification failed` when the token is the problem. |
| Webhook verified, but no message ever arrives | The Page is not subscribed: **Add subscriptions** in step 5. Or the sender has no role on an app in development mode (step 6). |
| `Invalid signature` in the log | `app_secret` is not this app's secret. |
| Comments or leads never reach the agent, and the start-up log says `Page events (comments and lead ads) are refused` | `app_secret` is not set (step 3). |
| Send fails with error 190 (`OAuthException`) | The Page token is no longer valid — generate a new one (step 2). |
| Send fails with "API calls from the server require an appsecret_proof argument" | **Require App Secret** is on and `app_secret` is not set. |
| Send fails with error 10 / 2018278 | The reply came more than 24 hours after the person's last message. |
| Send fails with a permissions error (code 200) | The token lacks `pages_messaging`, or the app has no Advanced access and the person has no role on it. |
| Messages stop after the server was down | Meta retries for a while; after about an hour of failures it disables the webhook for the Page and sends a "Webhooks Disabled" alert. Fix the endpoint, then subscribe again (step 5). |
