做网站需要什么内容,提高网站知名度,app开发用什么软件,推荐一款男人都懂得app一、归因概要
广告归因#xff0c;目的是用于衡量广告带来的激活用户的成本以及后续进一步的用户质量表现。 Apple Ads 广告平台是基于 App Store#xff08;站内广告#xff09;#xff0c;同时属于自归因平台#xff08;通常称为 SAN#xff09;。这两个因素#xff…一、归因概要
广告归因目的是用于衡量广告带来的激活用户的成本以及后续进一步的用户质量表现。 Apple Ads 广告平台是基于 App Store站内广告同时属于自归因平台通常称为 SAN。这两个因素决定了 ASA 与其他大部分广告平台站外广告的区别。 ASA 广告投放前无需创建投放链接、监测链接ASA 归因由 Apple 自身完成可以保证用户隐私同时还能做到用户级归因 目前获取归因数据可采用自归因或第三方归因的方式。
以下是该方案的要点和广告主应对措施 此归因方案仅适用 Apple Search Ads 广告仅支持 iOS 14.3 及更高版本14.3 之前的版本需使用 iAd Framework 从 2023 年 2 月 7 日开始iAd 框架停止用于归因 Apple Ads 的广告安装。 所有通过iAd Framework发送的请求都会收到 iad-attributionfalse的错误具体请查看iAd Changelog | Apple Developer Documentation 开发者需实施 AdSercices 框架以归因来自 Apple Ads 的广告安装。此框架于 2021 年 1 月发布如果已完成实施、或使用主流 MMP 的开发者无需做更改。 AdServices[3] 仅支持 iOS 14.3 及更高版本的设备。 届时 14.2 及更低旧版本的设备将无法归因苹果广告安装。 此方案将极大提高 ASA 广告安装的激活率 安装到激活的差距逐渐降至极低 此方案涉及前后端的系统开发需自己归因的开发者应尽早制定计划、安排实施 使用 MMP 服务的开发者与您的服务提供商沟通了解其SDK对此方案的支持进度 无论开发者自己实施还是采用 MMP均需要发布新的app版本 针对 iOS 14 及更高版本的设备LAT 的概念已失效受众的年龄和性别定向不再排除限制跟踪的用户 什么是ASA ASA(Apple Search Ads)也叫ASMApp Store Marketing---应用商店广告即用户在App Store 搜索应用时出现在搜索结果上方的广告。 搜索广告有三种展现形式标题icon截图/描述/视频 App 在搜索广告上的展示内容与其在自然搜索排名下的内容相同开发者不必也不能单独为搜索广告设置素材且无法选择或设置广告具体以哪种方式展现 ASA广告竞价逻辑
ASA广告类似谷歌搜索广告属于竞价广告模式以点击计费
广告展现量受5个因素影响分别是相关性、出价、关键词热度、竞争者、投放位置广告单价受3个因素影响分别是相关性、出价、竞争者
1相关性关键词和APP之间的相关性一个关键词和这个 App 之间的相关性是由这个 App 的元数据和用户 行为所决定的元数据即 App 的标题、icon、截图、描述等。
2出价在其他参数不变的情况下价高者得
其他影响因素产品权重安装总量/总榜/分类榜单越高是受到用户喜爱得产品、关键词热度aso吸引力-100关键词的覆盖
二、归因实施说明
归因业务流程图 这个归因方案包含两部分客户端的 AdServices 框架和从苹果Search Ads归因服务器获取归因数据的 RESTful API。
下图说明了结合使用 AdServices 框架和 RESTful API 来完成归因。 Request Token 获取 token (NSString *)attributionTokenWithError:(NSError * _Nullable *)error;
AdServices 框架返回的 token 为字符串类型并且只有 24 小时有效期。
AAAttributionErrorCode 归因错误枚举值
typedef enum AAAttributionErrorCode : NSInteger {// 没有返回 token网络不可用。AAAttributionErrorCodeNetworkError 1,// 没有返回 token发生了内部错误。AAAttributionErrorCodeInternalError 2,// 没有返回 token操作系统平台不支持。AAAttributionErrorCodePlatformNotSupported 3
} AAAttributionErrorCode;
Request attribution 获取归因数据
您可以将 token 提供给 MMP或者使用该 token 在 24 小时内进行 POST API 调用以获取归因数据。在请求正文中带上 token
POST https://api-adservices.apple.com/api/v1/
yourtokenyourtoken
Response Codes 返回状态码
Response状态码Description描述200成功。API 找到了匹配的归因记录返回值包含 attributiontrue。如果API没有找到对应的归因记录返回值为 attributionfalse。在这种情况状态码 200 仅表示服务器有数据返回。400token 无效。404没有找到。API 无法获取到请求的归因记录。Tokens 只有 24 小时的有效期。如果请求超过了 24 小时苹果会返回 404 状态码。如果 token 是有效的一个最佳实践最多重试 3 次每次间隔 5 秒钟。500服务器暂时关闭或无法访问。请求可能是有效的但是你需要选择合适的时间点进行重试。
代码示例:
- #import AdServices/AdServices.h
(void) methodToGetToken {if (available(iOS 14.3, *)) {NSError *error;NSString *token [AAAttribution attributionTokenWithError:error];if (token ! nil) {// 发送POST请求归因数据}} else {// Fallback on earlier versions}
}
- (void) attributionWithToken:(NSString *)token {NSURLSessionConfiguration *configuration [NSURLSessionConfiguration defaultSessionConfiguration];NSURLSession *session [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];NSURL *url [NSURL URLWithString:https://api-adservices.apple.com/api/v1/];NSMutableURLRequest
request [NSMutableURLRequest requestWithURL:urlcachePolicy:NSURLRequestUseProtocolCachePolicytimeoutInterval:60.0];[request addValue:application/json forHTTPHeaderField:Content-Type];[request setHTTPMethod:POST];
* NSData*postData [token dataUsingEncoding:NSUTF8StringEncoding];[request setHTTPBody:postData];NSURLSessionDataTask *postDataTask [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {NSError *resError;NSMutableDictionary *resDic [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:resError];}];[postDataTask resume];
} 字段类型说明attributionBoolean如果用户在应用下载前 30 天点击了 App Store、Apple News 以及 Stocks则其值为 true。如果 API 找不到匹配的归因记录则为false。orgIdInteger广告系列所属的账户 ID。campaignIdInteger广告系列 ID。conversionTypeString表明是否首次下载。Redownload 说明用户在本设备下载/卸载过或者用同一账户在其他设备下载过。clickDateDate/time string用户点击相应广告的日期和时间。此字段仅出现在详细归因数据包中。adGroupIdInteger广告组 ID。countryOrRegionString国家或地区。keywordIdInteger关键字的 ID。creativeSetIdInteger广告素材集的 ID。 这里只要是attributiontrue就意味着用户是通过点击广告下载的app。反之会返回false。原则是用户在下载app之前30天内是否有广告点击的记录。所以客户如果只想判断用户是不是通过广告带来的只看这个字段就可以true的情况下click date一定是在30天内的。
conversiontype是用来判断用户是首次安装还是重新安装对于判断用户是否来自广告投放没有太大的关系。
客户端调用AdService框架 AdServices framework发起调用请求生成 Token 。 AdServices framework生成Token 返回Token有效期24小时。更多详请请参阅 attributionToken 使⽤Token向Attribution API请求结果 Attribution API返回true or false判断该客户是否点击过苹果搜索广告 True: ⽤户在过去30天内曾经点击过苹果广告记录最近一次用户点击广告 False: ⽤户在过去30天没有点击过苹果广告用户为IOS14.3以下版本请求Token就会报错 MMP 或开发⼈员使⽤ Token 发起 RESTful API 获取归因记录请求苹果的归因服务器响应请求。更多详请请参阅Attribution Payload。 API 返回的归因数据中的键值与Apple Ads ⼴告系列管理 API 中的⼴告系列的字段相对应。更多详情请参阅 Attribution Payload Descriptions。
*苹果官网参考文档https://developer.apple.com/doc
三、ManagementAPI接⼊具体流程
授权广告主账户对应广告系列组“API只读” 需要广告主提供一个开发者账号AppleID即邮箱 授权后需要甲方到邮箱查收邀请邮件并点击邮件中链接确认授权
开发者生成公钥私钥准备上传到ads.apple.com后台 生成公钥私钥 如果您使用的是MacOS或类似UNIX的操作系统OpenSSL可以原生运行。如果您使用的是Windows平台则需要下载OpenSSL。 openssl ecparam -genkey -name prime256v1 -noout -out private-key.pemopenssl ec -in private-key.pem -pubout -out public-key.pem 上传公钥私钥生成clientid、keyid、teamid 登录被授权的账号查看右上⻆设置 粘贴前⾯⽣成的公钥进去点击保存按钮并记录⽣成的clientid、keyid、teamid 使⽤新⽣成的clientid、keyid、teamid⽣成client_secret 生成出来的ID示例
clientId SEARCHADS.aeb3ef5f-0c5a-4f2a-99c8-fca83f25a9
teamId SEARCHADS.hgw3ef3p-0w7a-8a2n-77c8-scv83f25a7
keyId a273d0d3-4d9e-458c-a173-0db8619ca7d7 使⽤3个id⽣成client_secret的示例
import jwt
import datetime as dt
client_id SEARCHADS.27478e71-3bb0-4588-998c-182e2b405577
team_id SEARCHADS.27478e71-3bb0-4588-998c-182e2b405577
key_id bacaebda-e219-41ee-a907-e2c25b24d1b2
audience https://appleid.apple.com
alg ES256
Define issue timestamp.
issued_at_timestamp int(dt.datetime.utcnow().timestamp())
Define expiration timestamp. May not exceed 180 days from issue timestamp.
expiration_timestamp issued_at_timestamp 86400*180
Define JWT headers.
headers dict()
headers[alg] alg
headers[kid] key_id
Define JWT payload.
payload dict()
payload[sub] client_id
payload[aud] audience
payload[iat] issued_at_timestamp
payload[exp] expiration_timestamp
payload[iss] team_id
Path to signed private key.
KEY_FILE private-key.pem
with open(KEY_FILE,r) as key_file:key .join(key_file.readlines())
client_secret jwt.encode(
payloadpayload,
headersheaders,
algorithmalg,
keykey
)
with open(client_secret.txt, w) as output:output.write(client_secret.decode(utf-8)) JWT请求头和请求data示例
Header
{
alg: ES256,
kid: bacaebda-e219-41ee-a907-e2c25b24d1b2
}
Payload
{
iss: SEARCHADS.hgw3ef3p-0w7a-8a2n-77c8-scv83f25a7, # 您上一步获得的teamId
iat: 2234567891, # 创建客户端密钥时的 UNIX UTC 时间戳
exp: 2234567900, # 客户端密码过期的 UNIX UTC 时间戳。该值必须大于当前时间
aud: https://appleid.apple.com,
sub: SEARCHADS.27478e71-3bb0-4588-998c-182e2b405577 # 您上一步获得的client_secret
} 请求⽣成的client_secret示例
eyJraWQiOiJiYWNhZWJkYS1lMjE5LTQxZWUtYTkwNy1lMmMyNWIyNGQxYjIiLCJhbGciOiJFUzI1NiJ9.
eyJpc3MiOiJEcmVhbWNvbXBhbnkiLCJhdWQiOiJBdXRoZW50aWNhdG9yIiwiZXhwIjoxNTcxNjcwNjIx
LCJuYmYiOjE1NzE2NjcwMjEsInN1YiI6Im11c3RlciIsImNsaWVudF9pZCI6ImFiY2QxMjM0IiwiYWRt
aW4iOiJ0cnVlIn0.s4C3p9kVNFeRAB5tChatC3ldQX07v9mG7thL7FeEO6cClfNuiaLSgq8f8ymbfO3O
QYW_KuwaA1KYRuoy1JmKk 4DBbYLcz6aoABe0pzI5Z_6wgMzAyqz8pQtwDAcd4Idoi8JdRbtzZce9o-0
nZiFA4hVAXqYwpEYC4UU8ZmJO_z8tY4juHPTV3nDugdtqyNnmAiBoLryOfGNngQZccdY1_QvkXS1y0bg1
a0k8cVVtnq- _93fYJIt9Z64CTvlH3uOeh7uaEv3nIxpXhvhkTySpUmY8e04TO09oTyZijiloByv3KFQ9
2OOJ8L 5N5_CeEc5p9LWjT1pcX8ATamOycZz2Q
使⽤client_secret请求苹果接⼝⽣成access_token
access_token存在过期的情况通常为1小时需要定时维护 请求获取 access_token 示例
curl -X POST \
-H Host: appleid.apple.com \
-H Content-Type: application/x-www-form-urlencoded \
https://appleid.apple.com/auth/oauth2/token?grant_typeclient_credentials
client_idSEARCHADS.27478e71-3bb0-4588-998c-182e2b405577client_secreteyJ0
eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.zI1NiIsImprdSI6Imh0dHBzOi8vYXV0aC5kZXYuYXB
pLnJpY29oL3YxL2Rpc2NvdmVyeS9rZXlzIiwia2lkIjoiMmIyZTgyMTA2NzkxZGM4ZmFkNzgxNW
Q3ZmI1NDRhNjJmNzJjMTZmYSJ9.eyJpc3MiOiJodHRwczovL2F1dGguZGV2LmFwaS5yaWNvaC8iL
CJhdWQiOiJodHRwczovL2lwcy5kZXYuYXBpLnJpY29oLyIsImlhdCI6MTQ5MDg1Mjc0MSwiZXhwI
joxNDkwODU2MzQxLCJjbGllbnRfaWQiOiI4ODQwMWU1MS05MzliLTQ3NzktYjdmNy03YzlmNGIzZj
kyYzAiLCJzY29wZSI6Imh0dHBzOi8vaXBzLmRldi5hcGkucmljb2gvdjEiLCJyaWNvaF9tc3Mi
OnsibWVkaWEiOnsicXVvdGEiOjEwLCJ0aHJvdHRsZSI6eyJ2YWx1ZSI6MCwid2luZG93IjowfX1
9fQ.jVq_c_cTzgsLipkJKBjAHzm8KDehW4rFA1Yg0EQRmqWmBDlEKtpRpDHZeF6ZSQfNH2OlrBW
FBiVDV9Th091QFEYrZETZ1IE1koAO14oj4kf8TCmhiG_CtJagvctvloW1wAdgMB1_Eubz9a8oim
cODqL7_uTmA5jKFx3ez9uoqQrEKZ51g665jSI6NlyeLtj4LrxpI9jZ4zTx1yqqjQx0doYQjBPhOB
06Z5bdiVyhJDRpE8ksRCC3kDPS2nsvDAal28sMgyeP8sPvfKvp5sa2UsH78WJmTzeZWcJfX2C2ba3
xwRMB5LaaVrQZlhj9xjum0MfDpIS1hJI6p5CHZ8wscopesearchadsorg 请求接⼝的字段说明
Header
headers {Content-Type: application/x-www-form-urlencoded,Host: appleid.apple.com
}
Payload
grant_type固定值client_credentials
client_id前面获取的client_id
client_secret前面获取的client_secret
scope固定值searchadsorg 响应示例
{access_token:eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwia2lkIjpudWxsfQ..lXm332TFi0u2E9YZ.bVVBvsjcavoQbBnQVeDiqEzmUIlaH9zLKY6rl36A_TD8wvgvWxpyBXMQuhs-qWG_dxQ5nfuJEIxOp8bIndfLE_4a3AiYtW0BsppO3vkWxMe0HWnzglkFbKUHU3PaJbLHpimmnLvQr44wUAeNcv1LmUPaSWT4pfaBzv3dMe3PNHJJCLVLfzNlWTmPxViIivQt3xyiQ9laBO6qIQiKs9zX7KE3holGpJ-Wvo39U6ZmGs7uK9BoNBPaFtd_q914mb9ChHAKcQaxF3Gadtu_Z5rYFg.vD0iQuRwHGYVnDy27qexCw,token_type: Bearer,expires_in: 3600,scope: searchadsorg
} 响应字段说明
access_token返回的 access_token用于请求苹果广告相关数据接口 https://developer.apple.com/documentation/apple_search_ads/calling_the_apple_search_ads_api
token_type固定值Bearer
expires_intoken有效期3600秒即1小时
scope访问权限
使⽤access_token获取⼴告账户下的⼴告orgid
官方文档Get User ACL | Apple Developer Documentation 请求示例
curl --location --request GET https://api.searchads.apple.com/api/v4/acls
--header Authorization: Bearer eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwia2
lkIjpudWxsfQ..FGvVpX2vBYfyC91X.FKDdsLoAZwsfE8lgd5Ts5vSTY-lScKSp2myVIP9Eh7H
wc-7vsXr63aHcvNaDec9p7MwFHYnCC8Zphuf-BoqhAgyEu3hg_oHuqNEhbyS7iZexDfdgScTop
zGD5IP7Kiag71n0bbKb8o68MlJV8P-faGcsZR1-FBuISuyIrP6ZdUwXjoNygNCr4RkOLLNLjng
ShnnFUICJ3Q7dvrMQk0kUI_OFP3OcfZrsbHOIPWgDxA0W6GYI000Ua9x3TPT46kW2RQ67Pk5RS
2Ft_sqPgVh8V-A.7Rs0pIsQR09nRccM8响应示例{orgName: org name example,orgId: 40669820,currency: USD,timeZone: America/Los_Angeles,paymentModel: PAYG,roleNames: [Admin],parentOrgId: 27154130,displayName: display name example
}
使⽤ access_token 获取⼴告账户投放数据
7.1 请求头参数说明 Authorization 设定值时组合⽅式为Bearer {access_token}即Bearer在前后⾯加 access_token值 X-AP-Context 的值为orgId{orgId}orgId不能漏掉 orgId 即广告系列组ID上面第6部分接口中获取到的orgId
7.2 请求示例 - 获取广告系列组下的广告组数据 以下仅为1个示例实际对接时可查看苹果官⽅Management API⽂档根据业务需要确认请求接⼝ 下面示例的官方文档参考 Get an Ad Group | Apple Developer Documentation
7.2.1 请求说明
URL
https://api.searchads.apple.com/api/v4/campaigns/{campaignId}/adgroups/{adgroupId}请求方式
GET请求头参数
Authorization
X-AP-ContextURL中参数
campaignId #广告系列ID
adgroupId #广告组ID
7.2.2 响应状态码
200 请求成功
401 无权限请检查请求参数
403 拒绝请求请检查请求参数
404 请求资源为找到请检查请求参数
500 苹果服务器异常稍后重试
7.2.3 响应示例
{id: 542370539,campaignId: 56543219,name: ad group name example,cpaGoal: {amount: 100,currency: USD},startTime: 2021-04-08T12:00:22.788,endTime: 2021-04-09T12:00:22.788,automatedKeywordsOptIn: false,defaultBidAmount: {amount: 100,currency: USD},pricingModel: CPC,targetingDimensions: {age: {included: [{minAge: 20,maxAge: 25},{minAge: 25,maxAge: 55}]},gender: {included: [M,F]},country: null,adminArea: null,locality: null,deviceClass: {included: [IPAD,IPHONE]},daypart: {userTime: {included: [1,3,22]}},appDownloaders: null},orgId: 40669820,modificationTime: 2020-04-08T19:00:24.105,status: ENABLED,servingStatus: RUNNING,servingStateReasons: null,displayStatus: RUNNING,deleted: false
}
示例代码
import jwt
import datetime as dt
import requests
import json
import time
client_id SEARCHADS.27478e71-3bb0-4588-998c-182e2b405577
team_id SEARCHADS.27478e71-3bb0-4588-998c-182e2b405577
key_id bacaebda-e219-41ee-a907-e2c25b24d1b2
audience https://appleid.apple.com
alg ES256
Define issue timestamp.
issued_at_timestamp int(dt.datetime.utcnow().timestamp())
issued_at_timestamp int(time.time())
Define expiration timestamp. May not exceed 180 days from issue timestamp.
expiration_timestamp issued_at_timestamp 600
Define JWT headers.
headers dict()
headers[alg] alg
headers[kid] key_id
Define JWT payload.
payload dict()
payload[sub] client_id
payload[aud] audience
payload[iat] issued_at_timestamp
payload[exp] expiration_timestamp
payload[iss] team_id
Path to signed private key.
KEY_FILE private-key.pem
with open(KEY_FILE,r) as key_file:
key .join(key_file.readlines())
key -----BEGIN EC PRIVATE KEY-----
MHcCAQEEINEGa2CfhaseOXzsHoya/UW4kgQsij9ZW6j3GQS2zEwoAoGCCqGSM49
AwEHoUQDQgAENEUUVYnQhWQRT3YEwT3m2VGS5jlO6lanvZgLDHkWfOXhBiUfF8
Cyz/X3bzbkP8pOdJZ901qGdyeW73RV1RmA
-----END EC PRIVATE KEY-----
获取 client_secret
client_secret jwt.encode(
payloadpayload,
headersheaders,
algorithmalg,
keykey
)
client_secret client_secret.decode(utf-8)
print(-------------------- client_secret --------------------)
print(client_secret)
获取 access_token
url https://appleid.apple.com/auth/oauth2/token?grant_typeclient_credentialsclient_id client_id client_secret client_secret scopesearchadsorg
token requests.post(urlurl)
token_json json.loads(token.content.decode())
access_token token_json[access_token]
print(-------------------- access_token --------------------)
print(access_token)
acls
url https://api.searchads.apple.com/api/v4/acls
headers {Authorization: Bearer %s % (access_token),Content-Type: application/json
}
acl requests.get(urlurl, headersheaders)
print(-------------------- acl --------------------)
print(acl.text)
org_id 123456
campaign_id 1234567890
headers {Authorization: Bearer %s % (access_token),Content-Type: application/json,X-AP-Context: orgId%d % (org_id),
}
获取 campaigns
url https://api.searchads.apple.com/api/v4/campaigns
campaigns requests.get(urlurl, headersheaders)
print(-------------------- campaign --------------------)
print(campaigns.text)
获取 campaign report
url https://api.searchads.apple.com/api/v4/reports/campaigns
data {# startTime: str(dt.date.today()),# endTime: str(dt.date.today()),startTime: 2022-09-01,endTime: 2022-09-01,selector: {orderBy: [{field: localSpend,sortOrder: ASCENDING}],conditions: [{field: campaignId,operator: EQUALS,values: [campaign_id],ignoreCase: False},{field: deleted,operator: IN,values: [false,true]}],pagination: {offset: 0,limit: 1000}},returnRowTotals: True,granularity: DAILY,timeZone: ORTZ,returnGrandTotals: True,returnRecordsWithNoMetrics: True
}
data json.dumps(data)
reports requests.post(urlurl, datadata, headersheaders)
print(-------------------- campaign reports --------------------)
print(reports.text)
package main
import (fmtio/ioutilnet/httpstringstimegithub.com/dgrijalva/jwt-go/v4
)
func main() {client_id : SEARCHADS.27478e71-3bb0-4588-998c-182e2b405577team_id : SEARCHADS.27478e71-3bb0-4588-998c-182e2b405577key_id : bacaebda-e219-41ee-a907-e2c25b24d1b2audience : https://appleid.apple.comalg : ES256issued_at_timestamp : time.Now().Unix()expiration_timestamp : issued_at_timestamp 600claim : jwt.MapClaims{sub: client_id,aud: audience,iat: issued_at_timestamp,exp: expiration_timestamp,iss: team_id,}private_pem : -----BEGIN EC PRIVATE KEY-----
MHcCAQEEINEGa2CfhaseOXzsHoya/UW4kgQsij9ZW6j3GQS2zEwoAoGCCqGSM49
AwEHoUQDQgAENEUUVYnQhWQRT3YEwT3m2VGS5jlO6lanvZgLDHkWfOXhBiUfF8
Cyz/X3bzbkP8pOdJZ901qGdyeW73RV1RmA
-----END EC PRIVATE KEY-----private_key, _ : jwt.ParseECPrivateKeyFromPEM([]byte(private_pem))token : jwt.NewWithClaims(jwt.SigningMethodES256, claim)token.Header map[string]interface{}{alg: alg,kid: key_id,}client_secret, _ : token.SignedString(private_key)fmt.Println(-------------------- client_secret --------------------)fmt.Println(client_secret)// 获取 access_tokenurl : https://appleid.apple.com/auth/oauth2/tokenres_token, err : http.Post(url, application/x-www-form-urlencoded, strings.NewReader(grant_typeclient_credentialsclient_idclient_idclient_secretclient_secretscopesearchadsorg))if err ! nil {fmt.Println(err.Error())}defer res_token.Body.Close()access_token, _ : ioutil.ReadAll(res_token.Body)fmt.Println(-------------------- access_token --------------------)fmt.Println(string(access_token))
}
ASAToken.java
- package com.appsa.asa;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.apache.commons.io.IOUtils;
import java.io.DataOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.interfaces.ECKey;
import java.util.Date;
import java.util.HashMap;
/**
apple ads java demo
*/
public class ASAToken {private static String client_id xxx;private static String team_id xxx;private static String key_id xxx;private static String aud https://appleid.apple.com;private static String alg ES256;// 生成私钥// openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem// 使用私钥生成公钥// openssl ec -in private-key.pem -pubout -out public-key.pem// 将PKCS1私钥转换为PKCS8(该格式一般Java调用)// openssl pkcs8 -topk8 -inform pem -in private-key.pem -outform pem -nocrypt -out private-key-new.pemprivate static final String PRIVATE_KEY_FILE_256 /Users/xxx/Downloads/private-key-new.pem;public static void main(String[] args) {System.out.println(client_id: client_id);System.out.println( team_id: team_id);System.out.println( key_id: key_id);try {String clientSecret createClientSecret(PRIVATE_KEY_FILE_256);System.out.println(clientSecret 建议保存有效期可设置最长 180 天);System.out.println(clientSecret);String url https://appleid.apple.com/auth/oauth2/token;String urlParameters grant_typeclient_credentialsscopesearchadsorgclient_id client_id client_secret clientSecret;byte[] postData urlParameters.getBytes(StandardCharsets.UTF_8);int postDataLength postData.length;URL obj new URL(url);HttpURLConnection con (HttpURLConnection) obj.openConnection();con.setDoOutput(true);con.setRequestMethod(POST);con.setRequestProperty(charset, utf-8);con.setRequestProperty(Content-Length, Integer.toString(postDataLength));con.setRequestProperty(Host, appleid.apple.com);con.setRequestProperty(Content-Type, application/x-www-form-urlencoded);con.setUseCaches(false);try (DataOutputStream wr new DataOutputStream(con.getOutputStream())) {wr.write(postData);}if (con.getResponseCode() 200) {String result IOUtils.toString(con.getInputStream(), StandardCharsets.UTF_8);JSONObject jsonObject JSONObject.parseObject(result);System.out.println(access_token 有效期1个小时);System.out.println(jsonObject.getString(access_token));}} catch (Exception e) {System.err.println(error);}}/*** Create a Client Secret** return client secret*/public static String createClientSecret(String privateKeyPath) throws Exception {Algorithm algorithm Algorithm.ECDSA256((ECKey) PemUtils.readPrivateKeyFromFile(privateKeyPath, EC));return JWT.create().withIssuer(team_id).withAudience(aud).withHeader(new HashMap() {{put(alg, alg);put(kid, key_id);}}).withSubject(client_id).withIssuedAt(new Date()).withExpiresAt(new Date(System.currentTimeMillis() 86400 * 180 * 1000L)).sign(algorithm);}
}
PemUtils.java
package com.appsa.asa;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class PemUtils {private static byte[] parsePEMFile(File pemFile) throws IOException {if (!pemFile.isFile() || !pemFile.exists()) {throw new FileNotFoundException(String.format(The file %s doesnt exist., pemFile.getAbsolutePath()));}PemReader reader new PemReader(new FileReader(pemFile));PemObject pemObject reader.readPemObject();byte[] content pemObject.getContent();reader.close();return content;}private static PublicKey getPublicKey(byte[] keyBytes, String algorithm) {PublicKey publicKey null;try {KeyFactory kf KeyFactory.getInstance(algorithm);EncodedKeySpec keySpec new X509EncodedKeySpec(keyBytes);publicKey kf.generatePublic(keySpec);} catch (NoSuchAlgorithmException e) {System.out.println(Could not reconstruct the public key, the given algorithm could not be found.);} catch (InvalidKeySpecException e) {System.out.println(Could not reconstruct the public key);}return publicKey;}private static PrivateKey getPrivateKey(byte[] keyBytes, String algorithm) {PrivateKey privateKey null;try {KeyFactory kf KeyFactory.getInstance(algorithm);EncodedKeySpec keySpec new PKCS8EncodedKeySpec(keyBytes);privateKey kf.generatePrivate(keySpec);} catch (NoSuchAlgorithmException e) {System.out.println(Could not reconstruct the private key, the given algorithm could not be found.);} catch (InvalidKeySpecException e) {System.out.println(Could not reconstruct the private key.);}return privateKey;}public static PublicKey readPublicKeyFromFile(String filepath, String algorithm) throws IOException {byte[] bytes PemUtils.parsePEMFile(new File(filepath));return PemUtils.getPublicKey(bytes, algorithm);}public static PrivateKey readPrivateKeyFromFile(String filepath, String algorithm) throws IOException {byte[] bytes PemUtils.parsePEMFile(new File(filepath));return PemUtils.getPrivateKey(bytes, algorithm);}
}
四、注意事项总结
1. 框架须知 AdServices.framework 这个框架是用于 ASA 归因的不受 ATT 约束就是无论用户是否允许跟踪都可以归因区别为是否能获取点击时间仅支持 14.3 及更高版本系统需 XCode 12.3 及更高版本支持,设置为 optional。 AppTrackingTransparency.framework 在 iOS 14 及更高版本用于征求用户跟踪许可的框架就是弹窗询问用户是否同意跟踪在 iOS 14.5 上苹果将强制要求开发者实施也是获取 IDFA 的前提。
2.token相关注意事项 AdServices 获取的 token 不可作为唯一设备标识 获取 token 需要设备联网,并且要做超时及容错处理 获取token后需等待五秒再发送请求给归因API 获取 token 的步骤可以借助日志系统收集相关信息用于排查问题和代码优化 APP首次激活时获取token建议获取网络权限后等待500ms-1000ms再做token请求 如果 App 退出前台在下一次打开进行 token 请求重试 对token请求成功率进行监测建议成功率下降5%应触发预警机制并根据报错调查原因 token 有效期为 24 小时 当 token 无效时接口响应码为 400 当 token 过期有效期为 24 小时时接口响应码为 404
3.数据归因相关注意事项 AdServices 的 restful api 请求失败后 每隔 5 秒重试建议最多 3 次 也可根据需求增加重试次数如重试之后仍然没有正常统计返回的报错日志 有数据差合理如进行反馈需提供 APP Id、发生问题的时间、API返回结果 Apple Search Ads归因窗口期为30天用户30天以前点击的广告我们无法归因 Apple Search Ads采用末次归因用户如果30天或一天内惦记了搜索标签又搜了关键词归因返回的是用户最后一次点击广告的信息 目前苹果搜索广告归因仅支持Adservice,并且Adservice仅支持14.3及以上的用户14.3以下的用户不会被归因到这些用户会被计入自然量 14.3以下的用户还可请求API,返回结果为attribution false 目前14.3以下的用户占比非常低过去4年推出的ipnone 90%使用ios1681%所有设备使用ios16
4.归因字段缺失原因 AdServices 归因在 ATT弹框之前不返回字段clickDate ATT弹框后用户不允许跟踪不返回字段clickDate AdServices 归因建议在 ATT 弹窗之后用户如允许跟踪则可以获得 clickDate 开启 Search Match搜索匹配的广告组带来的激活不返回字段 keywordId 默认素材带来的激活不返回字段 adId
5.客户端处理逻辑参考 由于各种原因导致的获取归因包失败时需要做容错处理及时进行重试必需 重试多次仍然失败的应用在下次启动时再进行获取必需 当归因包返回的 attribution 为 false7 天后再请求归因建议 iOS 14.3 以下设置项为限制跟踪时7 天后再次请求归因建议 当归因包返回的 attribution 为 true 时30 天后再请求归因建议 五、参考文档
Apple Ads 归因 API
Apple Ads 归因 API 文档 (PDF)
ad_services
Apple Search Ads