PAYATHON 2026

如何从 webhook 触发 actions.intent.INTENT_NAME

支付阿杰

结论

问题不在 Python 或 ngrok,而是 webhook 的返回格式和测试方式不对:

  1. Dialogflow v2 的 WebhookResponse 不支持顶层 data。Google Assistant 专用响应必须放在顶层 payload.google 中。由于当前响应无法解析,actions.intent.TRANSACTION_REQUIREMENTS_CHECK 并未发送给 Assistant。
  2. Int2 也必须启用 webhook fulfillment。否则,即使事件成功匹配,Dialogflow 也不会再次调用后端。
  3. 交易流程不能通过 Actions Simulator 验证,需要在登录了测试账号的 Assistant Android 或 iOS 真机上测试。
  4. webhook 不能像发送普通 HTTP 请求那样直接“调用”某个 actions.intent.*。它需要在当前响应中返回 systemIntent,由 Assistant 执行系统操作。操作完成后,Assistant 会通过 Dialogflow event 进入对应的 intent。
  5. 如果是现在新建的项目,还要注意旧版 Conversational Actions 已经停止服务。下面的方案只适用于当时仍支持该 API 的既有项目,或用于排查历史代码,无法让已经下线的平台恢复使用。

为什么没有收到检查结果

Dialogflow 已经给出了明确的错误信息:

Cannot find field: data in message google.cloud.dialogflow.v2.WebhookResponse

当前代码返回的是 Dialogflow v1 风格的结构:

{
  "data": {
    "google": {}
  }
}

Dialogflow v2 要求使用下面的结构:

{
  "payload": {
    "google": {}
  }
}

Dialogflow 无法解析原来的响应,因此请求在 Assistant 开始交易检查之前就失败了。Simulator 中显示的:

Webhook error (206)

只是 Assistant 对 webhook 解析错误的反馈,不代表交易检查已经执行。

修正后的 webhook

from flask import Flask, request, jsonify
from flask_cors import CORS

app = Flask(__name__)
CORS(app)


@app.route("/", methods=["POST"])
def index():
    data = request.get_json(silent=True) or {}
    query_result = data.get("queryResult", {})
    intent = query_result.get("intent", {}).get("displayName")

    if intent == "Int1":
        return jsonify({
            "payload": {
                "google": {
                    "expectUserResponse": True,
                    "isSsml": False,
                    "noInputPrompts": [],
                    "systemIntent": {
                        "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK",
                        "data": {
                            "@type": (
                                "type.googleapis.com/"
                                "google.actions.v2.TransactionRequirementsCheckSpec"
                            ),
                            "paymentOptions": {
                                "actionProvidedOptions": {
                                    "displayName": "VISA-1234",
                                    "paymentType": "PAYMENT_CARD"
                                }
                            }
                        }
                    }
                }
            }
        })

    if intent == "Int2":
        payload = (
            data.get("originalDetectIntentRequest", {})
                .get("payload", {})
        )

        # 实际结果通常位于 payload.inputs[*].arguments[*] 中。
        # 建议先记录完整 payload,再按收到的结构提取 resultType。
        result_type = find_transaction_result(payload)

        if result_type == "CAN_TRANSACT":
            message = "Transaction requirements check passed."
        else:
            message = (
                "Transaction requirements check result: "
                f"{result_type or 'UNKNOWN'}"
            )

        return jsonify({
            "fulfillmentText": message
        })

    return jsonify({
        "fulfillmentText": "Unsupported intent."
    })


def find_transaction_result(payload):
    for input_item in payload.get("inputs", []):
        for argument in input_item.get("arguments", []):
            if argument.get("name") == "TRANSACTION_REQUIREMENTS_CHECK_RESULT":
                extension = argument.get("extension", {})
                return extension.get("resultType")

    return None


if __name__ == "__main__":
    app.run(debug=True)

需要改成:

"payload": {
  "google": {
    "systemIntent": {}
  }
}

不能继续使用:

"data": {
  "google": {
    "systemIntent": {}
  }
}

Dialogflow 配置步骤

1. 配置 Int1

保留用户训练语句,例如:

Yes, I want

然后启用:

Enable webhook call for this intent

Int1 的 webhook 响应用于返回 systemIntent。

2. 配置 Int2

将 event 设置为:

actions_intent_TRANSACTION_REQUIREMENTS_CHECK

同时为 Int2 启用:

Enable webhook call for this intent

如果没有启用,Assistant 返回检查结果后,Dialogflow 即使匹配到了 Int2,也不会把请求转发给 Python 后端。

3. 从原始请求读取结果

交易检查结果不一定会通过普通的 queryResult.parameters 传入。在这个版本的 Google Assistant 集成中,结果通常位于:

originalDetectIntentRequest.payload.inputs[].arguments[]

参数名通常是:

TRANSACTION_REQUIREMENTS_CHECK_RESULT

可以在结果对象中检查:

extension.resultType

常见的成功状态是:

CAN_TRANSACT

字段的实际位置和结果值仍要以真机发送给 webhook 的请求为准。调试时可以记录经过脱敏的完整 originalDetectIntentRequest.payload,但不要记录支付凭据或其他敏感信息。

如何从 webhook 触发 actions.intent.INTENT_NAME

actions.intent.* 是 Assistant 平台的系统 intent,不是后端可以随意调用的 Dialogflow intent。完整流程如下:

用户输入
  → Dialogflow 匹配 Int1
  → Dialogflow 调用 webhook
  → webhook 返回 systemIntent
  → Assistant 执行系统操作
  → Assistant 把结果作为 actions.intent.* 返回 Dialogflow
  → 对应 event 匹配 Int2
  → Dialogflow 调用 Int2 的 webhook

因此,这里的“触发”系统 intent,是指返回下面的响应:

{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "systemIntent": {
        "intent": "actions.intent.INTENT_NAME",
        "data": {
          "@type": "对应的 protobuf 类型"
        }
      }
    }
  }
}

返回 followupEventInput、修改 queryResult,或者直接向自己的 webhook 发送请求,都不能代替 Assistant 的系统交易流程。

ngrok 和 Simulator 的影响

ngrok 通常不是问题,但需要满足这些条件:

  • Dialogflow fulfillment URL 使用有效的 HTTPS 地址;
  • ngrok 隧道保持在线,URL 没有变化;
  • 请求能在平台规定的时间内返回;
  • 响应的 Content-Type 是 application/json;
  • webhook 没有发生重定向,也没有被认证页面或本机防火墙阻断;
  • Flask 路由与 Dialogflow 中配置的 URL 完全一致。

Actions Simulator 不适合验证交易能力。交易检查可能依赖真实设备、测试账号、地区、账号设置和支付配置。测试时应使用支持 Google Assistant 的 Android 或 iOS 设备,并确认设备登录的是项目配置中的测试账号。

先修正 data 和 payload 的格式问题。确认 Dialogflow 能正确解析 webhook 响应后,再用真机验证交易检查以及 Int2 的回调。

备注:内容仅供参考。