一、API方案介绍 公众号关注-Open MKT API
在微信广告,以下简称MP投放端,推广公众号时,用户关注公众号之后会产生对应的转化行为(如下单、资料收集、注册等),为了量化关注类广告关注粉丝的转化效果,需要回传用户的转化行为。本API解决的是公众号内部转化行为回传的问题。
说明:
1)此文档说明与技术开发紧密相关,建议广告主运营人员在开发人员指导下阅读;
2)联调时,请以回传示例格式为准,确保联调正常;
使用须知
(一) 对接流程简介
特别说明:请使用已开通广告主身份的公众号或小程序身份进行联调,小游戏和游戏类小程序无法调用联调。
1.对于广告主账号与转化行为对应用户openid为同公司/个人主体时:在微信公众平台的『开发』—『基本配置』中注册成为开发者;
2.对于广告主账号与转化行为对应用户openid为不同公司/个人主体时,会报错:errcode:900351001:需要先通过跨主体回传功能获取小游戏账号授权,即回传的广告主账号需要获取小游戏账号的授权。详见授权操作说明;
3.创建转化数据对应的数据源;
4.获取用户openid,根据转化行为、推广目标,选择对应的行为类型回传数据,构造请求回传数据至对应的行为源,需要确保回传成功;
5.例行化:确保线上实际投放时,用户发生转化后,会自动实时运行步骤2,调用API回传数据,并回传成功;
6.验证:判断步骤4是否正常运行;
注:对于二次接入本API的广告主账号,可以跳过1、2步骤;
建议对接人员:建议后台开发同学对接本API;
(二) 问题咨询流程
开发过程中遇到任何问题可以参考以下流程解决
1.查看本文底部特别关注或【行为数据回传附录-常见问题解答】是否有相应说明;
2.根据【行为数据回传附录-返回码】,查询是否有相关说明,如果未检索到对应内容,访问公众平台开发者文档再次查询;
3.参见【行为数据回传附录-问题咨询流程】对接人工客服;
使用背景
(一) 目前支持的行为类型以及对应的智能投放优化目标、数据指标如下:
行为名称 | ActionType | 推广目标 | 数据指标 | 说明 |
下单 | COMPLETE_ORDER | 推广公众号 | 综合下单次数 | 如电商商品被下单 |
付费 | PURCHASE | 推广公众号 | 公众号内付费次数 | 如小说书城付费 |
预约 | RESERVATION | 推广公众号 | 公众号内填单人数 | 如提交联系信息,预约看车 |
注册 | REGISTER | 推广公众号 | 公众号内注册人数 | 如注册为会员 |
(二)目前支持的广告流量包括
1.公众号底部和文中;
2.朋友圈信息流;
3.小程序/小游戏广告位;
原理介绍
(一)对于公众号的粉丝或公众号内进行转化的用户,每个用户都会生成一个openid(参见公众平台获取openid文档),将openid与公众号appid作为转化数据的标识之一回传广告平台。
(二)广告平台会通过openid和appid关联到之前投放的公众号关注广告,从而得到用户行为与推广广告的关联。
(三)关联后计算的广告效果会呈现在MP投放端报表中(mp.weixin.qq.com),并会成为后续智能优化的数据基础。
二、对接步骤
概览
1、准备阶段
1)注册成为开发者; 2)创建数据源,获取存储数据的数据源ID;
2、正式对接
1)获取用户openid; 2)确定行为类型; 3)获取创建时存储的数据源ID 4)构造请求回传数据,需要确保回传成功;
3、例行化
确保线上实际投放时,用户发生转化后,会自动实时运行『2;、正式对接』
4、验证
调用API回传数据,并回传成功;
准备阶段:成为为公众平台开发者,创建数据源
(一) 注册成为开发者
目标:获取开发者ID、密码和access_token
步骤:
特别说明:请使用已开通广告主身份的公众号或小程序身份进行联调。
对于非同主体广告主账号:
- 用于回传数据的广告主账号需要先获取授权,即回传的广告主账号需要获取openid对应appid的授权。
注:openid对应appid需要先开通广告主身份,在官网,『推广』-『广告主』-『开通广告主』;
- 进行授权,详见授权操作流程
- 点击左边栏,『开发』下『开发配置』,获取AppSecret;
- 参考说明文档获取access_token
对于同主体广告主账号:
- 进入注册页面,点击左边栏,『开发』下『基本配置』。同意《微信公众平台开发者服务协议》后,点击『成为开发者』;
- 获取开发者密码。点击“重置”按钮:
- 弹窗提示,点击“确定重置”按钮:
- 点击“开始”按钮:
- 选择验证方式进行验证:
- 验证完成之后扫码绑定微信号,绑定成功后重置开发者密码。然后微信上点击确认,进行第二步密码验证:
- 密码验证后,即可查看开发者密码。由于开发者密码不会直接显示在MP后台,因此需要开发者保存。开发者密码为重要账号凭据,建议不要外传。
- 获取access_token。在开发——基本配置中点击“获取access_token”,用之前注册的开发者id和生成的开发者密码调用接口获取access_token。
(二) 创建数据源
目标:创建数据源,生成数据源ID(user_action_set_id) 步骤:
- 构造请求,创建数据源;
- 保存数据源ID,用于后续回传数据;
- 请求地址: user_action_sets/add
- 请求方法: POST
- 请求参数:
名称 | 类型 | 描述 |
Type | enum | 用户行为源类型,请填入『WECHAT』类型 |
Name | string | 用户行为源名称,必填 |
description | string | 用户行为源描述,字段长度最小 1 字节,长度最大 128 字节 |
wechat_app_id | string | 关注类广告投放账号对应的appid |
- 请求示例:
curl "https://api.weixin.qq.com/marketing/user_action_sets/add?version=v1.0&access_token=<ACCE SS_TOKEN>"\ -H 'Content-Type: application/json' \ -d '{ "type": "WECHAT", "wechat_app_id": "<WECHAT_APP_ID>", "name": "webuser_action_set", "description": "" }'
- 应答字段:
名称 | 类型 | 描述 |
user_action_set_id | integer | 用户行为源 id,通过 [user_action_sets 接口] 创建用户行为源时分配的唯一 id |
- 应答示例:
{ "errcode":0, " "errmsg":"" "data": { "user_action_set_id": "<USER_ACTION_SET_ID>" } }
正式对接:获取openid,确定行为类型,回传数据
(一) 获取用户openid
目标:获取访问小游戏用户的openid,作为回传数据的用户标识;
步骤:
1.获取用户openid,详见openid获取说明
2.用户发生转化时,记录发生转化的openid;
(二) 确定行为类型
目标:请与业务人员沟通确认,根据推广目标和oCPM优化目标,确认需要回传的行为类型; 步骤: 根据以下建议,确认后续API回传时需要填写的Action_Type:
行为名称 | ActionType | 推广目标 | 数据指标 | 说明 |
下单 | COMPLETE_ORDER | 推广公众号 | 综合下单次数 | 如电商商品被下单 |
付费 | PURCHASE | 推广公众号 | 公众号内付费次数 | 如小说书城付费 |
预约 | RESERVATION | 推广公众号 | 公众号内填单人数 | 如提交联系信息,预约看车 |
注册 | REGISTER | 推广公众号 | 公众号内注册人数 | 如注册为会员 |
(三)回传数据
目标:根据openid、Action_Type、创建时保存的数据源ID,调用API进行回传,获取成功应答;
步骤:
- 前提 在传入转化行为数据之前,请确保开发者已经:
- 注册成为开发者,并获取了可用的在有效期内的token
- 已创建数据源,并保留user_action_set_id,查询已有的user_action_set_id参见附录;
- 确认上报数据字段,参数说明如下:
名称 | 类型 | 必填 | 限制 | 说明 |
user_action_set_id | integer | yes | 用于标识数据归属权。 | |
url | string | yes | 转化行为发生页面的URL,小游戏需要在页面路径前增加『http://www.』 , 或直接填写 『腾讯首页 』 开头; | |
action_time | integer | yes | 行为发生时,客户端的时间点。广告平台使用的是GMT+8的时间,容错范围是前后10秒,UNIX时间,单位为秒,长度为10位。如果不填将使用服务端时间填写 | |
action_type | enum | yes | 预定义的行为类型,详见上方ActionType定义 | |
openid | string | yes | 根据获取到的openid,为28位字符 | |
wechat_app_id | string | yes | 生成该openid对应的账号appid,详见openid获取说明 | |
outer_action_id | string | no | 去重标识,平台会基于user_action_set_id,outer_action_id 和action_type三个字段做去重 。如果历史上报数据中存在某条数据的这三个字段与当前上报数据完全一样的,则当前数据会被过滤掉。 | |
action_param | string | no | ||
value | Int | no | 代表订单金额,单位为分,需要填写到param中获取,例如商品单价40元,需赋值为4000 | |
source | string | Yes | 转化数据发生的渠道: | |
归因方式claim_type | integer | Yes | 归因方式: |
- 调用API,回传数据 示例:需要统计“关注公众号”后发生的转化行为,开发者只需要在发生转化时,上报action_type=“COMPLETE_ORDER”,且附上获取的 openid。
- 回传付费请求示例:
curl -k 'https://api.weixin.qq.com/marketing/user_actions/add?version=v1.0&access_token=<ACCESS_TOKEN>"
-d '{
"actions":[
{
"user_action_set_id":"<USER_ACTION_SET_ID>",
"url":"<URL>",
"action_time":1513077790,
"action_type":"COMPLETE_ORDER",
"user_id":{
"wechat_app_id":"<WECHAT_APP_ID>",
"wechat_openid":"<WECHAT_OPEN_ID>"
},
"action_param":{
"object":"...",
"product_name":"..",
"product_id":"12345",
"source":"Biz",
"claim_type":1
}
}
]
}'
-H "Content-Type:application/json"
注:actions为数组列表,不能大于 50KB。数组最小长度 1,最大长度 50。
action_param 可以为空
- 应答示例:
正确的返回值为:
{"errcode":0," errmsg ":""}
返回错误返回码时,请使用【行为数据回传附录】进行排查
例行化:确保线上实际投放时,用户发生转化后,会自动实时运行第2步「正式对接」
(一)确保广告实际投放中,用户发生转化后,步骤四能够正常运行,自动获取用户的openid,并通过API回传到广告系统中。
(二)增加API数据回传日志,记录每次调用API回传的内容以及API应答信息,方便后续排查问题
实现demo
1.公众号关注发消息增加h5链接端,h5实现用户自动授权,并回传数据信息
//1新增方法一h5链接端开始页
public function actionIndex()
{
$sign = Yii::$app->request->get('sign');//sign 20210312
Yii::info('info','sign='. json_encode($sign));
$user_agent = $_SERVER['HTTP_USER_AGENT'];
if (strpos($user_agent, 'MicroMessenger') === false) {
// 非微信浏览器禁止浏览
die('非微信浏览器禁止浏览') ;
}
echo <<<html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>碧生源会员俱乐部</title>
</head>
<body>
<img border="0" src="https://bsy-mini.aolixinyy.com/qrcode_share_image.jpg" width="auto;" height="auto;">
</body>
</html>
html;
//return $this->actionIndex1($sign);
if($sign =='20210312') {
return $this->actionWe('', '', $sign);
}
}
//2 获取code去静默授权调用api接口返回回传数据
public function actionWe($code='', $redirectUrl='',$sign='')
{
$sign = Yii::$app->request->get('sign');//sign 20210312
$user_agent = $_SERVER['HTTP_USER_AGENT'];
if (strpos($user_agent, 'MicroMessenger') === false) {
// 非微信浏览器禁止浏览
die('非微信浏览器禁止浏览') ;
}
$state = self::STATE;//'111';
$code = Yii::$app->request->get('code');
$cache = Yii::$app->cache;
// 用户在微信中, 并且没有关联 openid
if ($code) {
// 通过 code 获取 open_id
$scope = 'snsapi_base';
try {
//第二步:通过code换取网页授权access_token
$twoUrl = sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
self::APP_ID, self::APP_SECRET, $code);
$tokenRes = json_decode($this->_tool_get($twoUrl),TRUE);
if(isset($tokenRes['errcode'])){
$url = self::APP_URL."/club/index";
//code已经被用过直接返回
header("location:{$url}");die();
}
if(isset($tokenRes)){
$openid = !empty($tokenRes['openid']) ? trim($tokenRes['openid']) :'';
$access_token = !empty($tokenRes['access_token']) ? trim($tokenRes['access_token']) :'';
$refresh_token = !empty($tokenRes['refresh_token']) ? trim($tokenRes['refresh_token']) :'' ;
$scope = !empty($tokenRes['scope']) ? trim($tokenRes['scope']) : $scope;
//记录日志
Yii::info('info','获取用户openid结果是'. json_encode($tokenRes));
// echo $openid.'=='.$access_token.'_'.$refresh_token;die('ooo');
$hasOpenid = $cache->get('openid'.$openid);
//调用接口并返回 todo
if(!empty($openid) && empty($hasOpenid)){
$arr = $this->actionApi($openid,$refresh_token);
if(isset($arr['errcode'])){
$access_token = $this->actionAccess_token(40001);
$arr = $this->actionApi($openid,$access_token);
Yii::info('info','wechat_user再次去请求回传接口arr返回回传结果成功='. json_encode($arr));
$cache->set('openid'.$openid, $openid, 86400);
}
}else{
Yii::info('info','openid 已经请求过再次请求拦截'. $hasOpenid);
}
}
///echo '<pre>';print_R($tokenRes);
$url = self::APP_URL."/club/index";
header("location:{$url}");die();
} catch (Exception $e) {
//echo "获取用户openid失败,失败原因是:" . $e->getMessage() . ",文件是:" . $e->getFile() . ",行数是:" . $e->getLine();echo '<pre>';
Yii::info('error','获取用户openid失败,失败原因是' . $e->getMessage() . ",文件是:" . $e->getFile() . ",行数是:" . $e->getLine());
}
} else {
//需要在微信里打开链接 获取code
//http://club-bsy.vxwei.com/?code=071DEB0w3Yc30W2B8s2w3UP1L72DEB0K&state=
// 静默授权,跳转获取 code
$url = sprintf("https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&scope=snsapi_base&state=".$state."&connect_redirect=1#wechat_redirect",
self::APP_ID, self::APP_URL. '/club/we/' . $redirectUrl);
header("location:{$url}");
die();
//
}
}
//3
/**
* 创建数据源 19064137
* 1111597080 WEB,1111523317 WECHAT
*目标:创建数据源,生成数据源ID(user_action_set_id) 步骤:
*构造请求,创建数据源;
*保存数据源ID,用于后续回传数据;
*请求地址: user_action_sets/add
*请求方法: POST
*请求参数:
*名称 类型 描述
*Type enum 用户行为源类型,请填入『WECHAT』类型
*Name string 用户行为源名称,必填
*description string 用户行为源描述,字段长度最小 1 字节,长度最大 128 字节
*wechat_app_id string 关注类广告投放账号对应的appid
*特别关注:需要填写被推广的公众号的appid
*/
public function actionCreate_source()
{
$cache = Yii::$app->cache;
$user_action_sets_source = $cache->get('key_user_action_sets');
if ($user_action_sets_source === false) {
/*
$url = "https://api.weixin.qq.com/marketing/user_action_sets/add?version=".self::VERSION."&access_token=".$this->actionAccess_token();
$param =
[
"type" => "WEB",//WECHAT
"wechat_app_id" => self::APP_ID,
"name" => self::RESERVATION,
"description" => "Hi,亲爱的~你还在为减肥苦恼吗?"
];
$info = $this->_tool_post($url,$param);
//{"errcode":900351000,"errmsg":"could create only one web action set for 19064137 with existed one : 1111597080"}
//echo $info;
$arr = json_decode($info, true);
*/
//1111597080 WEB,
//1111523317 WECHAT
$user_action_sets_source = 1111523317;
$cache->set('key_user_action_sets', $user_action_sets_source, 5400);
}
return $user_action_sets_source;
}
/**
* 获取access_token, 并保存到缓存中
* Author: ren hui
* Email: renhui@besunyen.com
* Date: 2021-03-12 09:31:05
* @return string
*/
public function actionAccess_token($errcode ='')
{
$cache = Yii::$app->cache;
$errcode = isset($errcode) ? $errcode : Yii::$app->request->get('errcode');//sign 20210312
if($errcode == 40001){
$cache->flush();
}
$access_token = $cache->get('access_token');
if ($access_token === false || $errcode == 40001) {
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".self::APP_ID."&secret=".self::APP_SECRET ;
$info = $this->_tool_get($url);
$arr = json_decode($info, true);
if(isset($arr['access_token'])){
$access_token = $arr['access_token'];
//print_r($arr['access_token']);
$cache->set('access_token', $access_token, 5400);
}
}
return $access_token;
}
/**
* curl get请求
* Author: ren hui
* Email: renhui@besunyen.com
* Date: 2021-03-11 19:31:05
* @param $url
* @return mixed
*/
public function _tool_get($url) {
$oCurl = curl_init();
if (stripos($url, "https://") !== FALSE) {
curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false);
}
curl_setopt($oCurl, CURLOPT_URL, $url);
curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, true);
$sContent = curl_exec($oCurl);
curl_close($oCurl);
return $sContent;
}
/**
* curl post 请求
* Author: ren hui
* Email: renhui@besunyen.com
* Date: 2021-03-11 19:31:05
* @param $url
* @param $param
* @return mixed
*/
public function _tool_post($url, $param) {
$oCurl = curl_init();
if (stripos($url, "https://") !== FALSE) {
curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false);
}
$strPOST = http_build_query($param);
curl_setopt($oCurl, CURLOPT_URL, $url);
curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($oCurl, CURLOPT_TIMEOUT, 10);
curl_setopt($oCurl, CURLOPT_POST, true);
curl_setopt($oCurl, CURLOPT_POSTFIELDS, $strPOST);
curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, true);
$sContent = curl_exec($oCurl);
curl_close($oCurl);
return $sContent;
}