You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Facebook评论机器人无法刷新access_token求助(无SDK)

Facebook评论回复机器人Access Token刷新解决方案

核心问题分析

你当前的代码逻辑存在本质问题:当webhook检测到token失效时,你试图在Facebook服务器发起的POST请求里触发授权跳转/抓取授权页,但授权流程必须由用户(管理员)在浏览器中主动完成——服务器端直接请求授权URL无法获取到需要用户登录授权的code,这就是你手动访问能拿到code但代码里抓不到的原因。

解决方案步骤

1. 拆分流程:区分Webhook回调、授权入口、授权回调

  • Webhook只负责处理评论回复逻辑
  • 单独的授权入口供管理员手动触发授权流程
  • 授权回调用于捕获code并交换为长期token+refresh_token

2. 优先用Refresh Token刷新(避免重复授权)

Facebook的短期access_token(2小时)可以用refresh_token(长期有效,除非用户取消授权)直接刷新,无需重新走授权流程,这是更高效的方式。

3. 持久化存储Token

把access_token和refresh_token存在数据库或配置文件中,不要硬编码在代码里。

修改后的代码实现

(1)Webhook核心逻辑(原fbwebhook.php修改)

<?php
// 1. Webhook验证逻辑
if(isset($_GET['hub_mode']) && isset($_GET['hub_challenge']) && isset($_GET['hub_verify_token'])){
    if($_GET['hub_verify_token'] == '1234567890'){
        echo $_GET['hub_challenge'];
        exit;
    }
}

// 2. 授权回调逻辑(处理code参数)
if(isset($_GET['code'])) {
    handleAuthCallback($_GET['code']);
    exit;
}

// 3. 处理评论回复的Webhook POST请求
$feedData = file_get_contents('php://input');
$data = json_decode($feedData);

if($data->object == "page"){
    // 从存储中获取最新的access_token(示例:从文件读取)
    $accessToken = getStoredAccessToken();
    $refreshToken = getStoredRefreshToken();
    
    $commentID = $data->entry[0]->changes[0]->value->comment_id;
    $url = 'https://graph.facebook.com/v15.0/me/messages?access_token='.$accessToken;
    
    $reply = "Thanks for your comment";
    $btn1 = array("type"=>"postback","title"=>"OPTION 1","payload"=>"BUDGET_10_PAYLOAD");
    $btn2 = array("type"=>"postback","title"=>"OPTION 2","payload"=>"BUDGET_20_PAYLOAD");
    $postData = json_encode([
        "recipient" => ["comment_id" => $commentID],
        "message" => [
            "attachment" => [
                "type" => "template",
                "payload" => [
                    "template_type" => "button",
                    "text" => $reply,
                    "buttons" => [$btn1,$btn2]
                ]
            ]
        ]
    ]);
    
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    $responseData = json_decode($response);
    
    // 检测token失效错误(错误码190是典型的token过期/无效)
    if($httpCode != 200 && isset($responseData->error->code) && $responseData->error->code == 190){
        // 尝试用refresh_token刷新access_token
        $newToken = refreshAccessToken($refreshToken);
        if($newToken){
            // 保存新的access_token
            saveAccessToken($newToken);
            // 重新发送回复请求
            resendCommentReply($commentID, $newToken);
        } else {
            // 刷新失败,记录日志并提示管理员手动授权
            error_log("Facebook token refresh failed, please re-authorize.");
            // 这里可以发邮件/消息通知管理员访问授权入口
        }
    }
}

http_response_code(200);

// --------------------- 辅助函数 ---------------------
// 从存储获取access_token(示例:从文件读取)
function getStoredAccessToken(){
    return file_get_contents('fb_token.txt');
}

// 从存储获取refresh_token
function getStoredRefreshToken(){
    return file_get_contents('fb_refresh_token.txt');
}

// 保存access_token到存储
function saveAccessToken($token){
    file_put_contents('fb_token.txt', $token);
}

// 用refresh_token刷新access_token
function refreshAccessToken($refreshToken){
    $appId = "4444444444444444";
    $appSecret = "ee....8";
    
    $url = "https://graph.facebook.com/v15.0/oauth/access_token?".http_build_query([
        "client_id" => $appId,
        "client_secret" => $appSecret,
        "grant_type" => "fb_exchange_token",
        "fb_exchange_token" => $refreshToken
    ]);
    
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $response = curl_exec($ch);
    curl_close($ch);
    
    $data = json_decode($response);
    if(isset($data->access_token)){
        // 如果返回新的refresh_token(部分场景会更新),也保存
        if(isset($data->refresh_token)){
            file_put_contents('fb_refresh_token.txt', $data->refresh_token);
        }
        return $data->access_token;
    }
    return false;
}

// 重新发送评论回复
function resendCommentReply($commentID, $accessToken){
    $url = 'https://graph.facebook.com/v15.0/me/messages?access_token='.$accessToken;
    $reply = "Thanks for your comment";
    $btn1 = array("type"=>"postback","title"=>"OPTION 1","payload"=>"BUDGET_10_PAYLOAD");
    $btn2 = array("type"=>"postback","title"=>"OPTION 2","payload"=>"BUDGET_20_PAYLOAD");
    $postData = json_encode([
        "recipient" => ["comment_id" => $commentID],
        "message" => [
            "attachment" => [
                "type" => "template",
                "payload" => [
                    "template_type" => "button",
                    "text" => $reply,
                    "buttons" => [$btn1,$btn2]
                ]
            ]
        ]
    ]);
    
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
    curl_exec($ch);
    curl_close($ch);
}

// 处理授权回调,交换code为token
function handleAuthCallback($code){
    $appId = "4444444444444444";
    $appSecret = "ee....8";
    $redirectUri = 'https://example.com/robots/jotabot-comment-replier/fbwebhook.php';
    
    $tokenUrl = "https://graph.facebook.com/v15.0/oauth/access_token?".http_build_query([
        "client_id" => $appId,
        "client_secret" => $appSecret,
        "redirect_uri" => $redirectUri,
        "code" => $code,
        "grant_type" => "authorization_code"
    ]);
    
    $ch = curl_init($tokenUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $response = curl_exec($ch);
    curl_close($ch);
    
    $data = json_decode($response);
    if(isset($data->access_token) && isset($data->refresh_token)){
        // 保存access_token和refresh_token
        file_put_contents('fb_token.txt', $data->access_token);
        file_put_contents('fb_refresh_token.txt', $data->refresh_token);
        // 提示授权成功
        echo "Token refreshed successfully!";
    } else {
        echo "Failed to exchange code: ".json_encode($data);
    }
}
?>

(2)手动授权入口(可以单独写一个页面,比如fb-authorize.php)

<?php
$appId = "4444444444444444";
$redirectUri = urlencode('https://example.com/robots/jotabot-comment-replier/fbwebhook.php');
$scope = urlencode('pages_messaging pages_read_engagement'); // 按需添加需要的权限

// 构造授权URL,包含必要参数
$authUrl = "https://graph.facebook.com/v15.0/oauth/authorize?".http_build_query([
    "client_id" => $appId,
    "redirect_uri" => $redirectUri,
    "scope" => $scope,
    "response_type" => "code",
    "state" => md5(uniqid()) // CSRF防护,可选但推荐
]);

// 跳转到Facebook授权页
header("Location: ".$authUrl);
exit;
?>

关键注意事项

  • 权限配置:确保你的Facebook应用已经添加了pages_messaging、pages_read_engagement等必要权限,并且管理员已经完成应用审核(如果要公开使用)。
  • Token存储:示例中用文件存储token,生产环境建议用数据库,并且加密存储。
  • 错误处理:添加完善的日志记录,方便排查token刷新失败的原因。

内容的提问来源于stack exchange,提问作者Diego

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 11:20:27