概要:本文供AppsFlyer客户(通常称为广告主或应用所有者)使用。应用所有者可通过在平台上通过OpenDSR(数据主体请求)API来应用使用与归因情况,以满足相关数据保护法规的合规要求。相关法规包括:GDPR(欧洲《通用数据保护条例》)、CCPA(美国加州《消费者隐私法案》)、LGPD(巴西《通用数据保护法》)、PDPA(泰国《个人数据保护法》)、PIPA(韩国《个人信息保护法》)。
法律提示: 以下内容仅供参考,不可作为法律意见使用。请咨询法律顾问及其他专业顾问,以明确GDPR、CCPA或其他相关法律是否适用。 您与AppsFlyer之间的隐私关系受《AppsFlyer服务隐私政策》约束。如果您对我们的服务隐私政策有任何疑问,或需要联系我们的数据保护负责人,请发邮件至privacy@appsflyer.com。为帮助您遵守《通用数据保护条例》(General Data Protection Regulation)第27条的规定,特此提供AppsFlyer欧盟境内代表人的联系信息:AppsFlyer Germany GmbH, Schönhauser Allee 180, 10119 Berlin, Germany(联系方式:privacy@appsflyer.com/strong>;+49-30-166373500)。
隐私法规
本文提及的隐私法规包括下表中的相关法规:
| 法规 | 图标 | 说明 |
|---|---|---|
| GDPR | 《通用数据保护条例》 (GDPR) 是欧盟法律中关于欧盟公民数据保护和隐私的一项条例。 | |
| CCPA | 《加州消费者隐私法案》 | |
| LGPD | 《一般数据保护法》 | |
| PDPA | 《个人数据保护法》 |
在本文中,隐私法规、GDPR、CCPA、LGPD、PDPA等术语可互换使用。
OpenDSR倡议
为了响应和处理数据主体根依据隐私法规提交的请求,AppsFlyer联合mParticle、Amplitude和Braze推出了OpenGSR协议(原OpenGDPR)。
OpenGSR是一个统一的开源框架,旨在技术公司之间的合作, 以公正透明地使用消费者数据。该框架使得供应商能够在多个系统中执行数据隐私操作, 以处理和存储客户数据。
定义
| DSR术语 | AppsFlyer条款 | 说明 |
|---|---|---|
| 数据对象 | 应用用户或终端用户 | 被收集数据的应用用户 |
| 数据控制者 | App所有者/广告主 | 应用所有者决定处理个人数据的目的和方式。 |
| 数据处理者 | AppsFlyer及其合作伙伴 | AppsFlyer及其合作伙伴代表数据控制人处理个人数据 |
DSR要求
DSR详细说明了数据对象的强制性权利,广告主必须遵守这些权利。在API使用场景中,这些权利被转换为不同的请求类型。以下为AppsFlyer处理各类请求的方式:
|
请求类型 (权利) |
GDPR定义 | AppsFlyer处理请求 |
|---|---|---|
| 访问权 |
|
应用所有者将收到用户被处理的个人数据副本。 |
| 转移权 | App用户有权以结构化、常用和机器可读的格式 (如 CSV 文件) 接收其所有个人数据。 | 应用程序所有者会收到应用程序用户处理过的个人数据的副本。 |
| 更正权 | 如果用户发现数据不准确或不真实,有权要求更正。应用程序所有者必须删除或更正不准确或不完整的数据。 |
|
| 删除权 | 用户有权要求删除个人数据,应用所有者需在收到请求后10天内完成。 | 删除数据 |
AppsFlyer的GDPR请求API
您可以使用本文介绍的GDPR请求API来实现DSR合规处理。
- GDPR请求: 执行上述请求类型之一:"访问"、"转移"、"删除" 或 "更正"。
- 状态请求:查询GDPR请求的当前状态
- 查询请求: 查询支持的API版本和数据格式
- 取消请求: 取消pending(待处理)阶段的GDPR请求
应用所有者必须在应用中增加UI功能,以响应用户的请求。注意,GDPR请求每次仅限一个用户。
1. GDPR请求
GDPR请求流程
GDPR请求类型(访问、转移、更正、删除)的处理流程如下:
- 用户提交请求。
- 应用所有者构建GDPR请求(请参见下文)并将其发送至AppsFlyer。
- AppsFlyer接收请求,如果有效,将返回 “201 OK”。
-
访问和转移请求:8天内完成。
注意:此8天期限从应用所有者提交请求时开始计算。如状态将从“待处理(pending)” 变为“处理中(in progress)”,请求将在6天内完成,总计8天。
删除与更正请求:- 前48小时,请求处于排队和待处理状态。用户可在此期间取消请求。
- 48小时后,请求状态变为处理中。AppsFlyer发送状态变更通知。此时,请求无法取消。
-
在10天内(见后文说明),AppsFlyer完成请求,请求状态变更为已完成。AppsFlyer发送状态变更通知。
- 在删除或更正的情况下,应用程序用户的数据将被删除。
- 在转移或访问的情况下,可以在AppsFlyer控制面板中的GDPR部分或通过请求API(见下文)访问终端用户数据。
请注意:此10天期限从应用所有者提交请求时开始计算。48小时后,请求状态将变为处理中,此后将在8天内完成,总共10天。
GDPR请求格式
GDPR请求API可以通过HTTP POST提交到以下端点:
https://hq1.appsflyer.com/api/gdpr/v1/opendsr_requests
对于API授权,请使用与Pull API相同的V2 API令牌。管理员可在面板的API token页面获取令牌。在请求头中加入:
参数说明
| 参数名称 | 是否必须配置 | 说明 |
|---|---|---|
| subject_request_id | 是 | UUID v4 字符串。由控制器提交请求时生成,可用于查询、更新或取消请求。 |
| subject_request_type | 是 | 表示GDPR请求类型的字符串值。支持的值:
|
| subject_identities | 是 |
|
| submitted_time | 是 |
|
| property_id | 是 |
表示本次请求所对应移动应用的字符串: 示例:
|
| api_version | 否 | 表示所需GDPR请求API版本的字符串版本 |
| status_callback_urls | 否(但推荐填写) |
|
|
platform |
否 | 值应为以下受支持的DSR平台之一: 可能出现的值为 android、ios、web、windowsphone
|
| 是 |
如为CTV、PC或主机端请求,可使用以下平台: |
Subject_identities对象
| 对象类型 | 是否必须配置 | 说明 |
|---|---|---|
| identity_type | 是 |
|
| identity_value | 是 |
|
| identity_format | 是 |
|
举例来说,GDPR数据删除请求
以下是在android, ios, web, windowsphone平台上的删除请求的示例:
curl --location --request POST 'https://hq1.appsflyer.com/api/gdpr/v1/opendsr_requests' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer %AuthTokenv2% \
--data-raw '{
"subject_request_id":"f4e5a271-f25e-4107-b681-************",
"subject_request_type":"erasure",
"submitted_time":"2020-07-05T10:00:00Z",
"platform": "android",
"subject_identities":[
{
"identity_type":"android_advertising_id",
"identity_value":"55*****-****-****-************",
"identity_format":"raw"
}
],
"api_version":"0.1",
"property_id":"com.*********.*******.********",
"status_callback_urls":[
"https://examplecontroller.com/opengdpr_callbacks"
]
}'
以下是CTV, PC, 和Console平台上删除请求的示例:
curl --location --request POST 'https://hq1.appsflyer.com/api/gdpr/v1/opendsr_requests' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer %AuthTokenv2% \
--data-raw '
{"requester": "test@email.com",
"subject_request_id":"valid-uuid4-string",
"subject_request_type":"erasure",
"submitted_time":"2020-07-05T10:00:00Z",
"subject_identities":[
{
"identity_type":"appsflyer_id",
"identity_value":"valid-appsflyer-id-string",
"identity_format":"raw"
}
],
"api_version":"0.1",
"property_id":"app-id",
"platform": "roku",
"status_callback_urls":[
"https://examplecontroller.com/opengdpr_callbacks"
]
}'
GDPR数据删除请求代码示例
/*
using the okhttp package install from maven
https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
*/
import okhttp3.*;
import java.io.IOException;
public class GdprSendRequest {
public static void main(String[] args){
OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(null, "" +
"{\r\n\"subject_request_id\": \"\"," +
"\r\n\"subject_request_type\": \"erasure\"," +
"\r\n\"platform\": \"android\"," +
"\r\n\"submitted_time\": \"2018-11-02T15:00:00Z\"," +
"\r\n\"subject_identities\": [\r\n" +
"{\r\n\"identity_type\": \"android_advertising_id\"," +
"\r\n\"identity_value\": \"\"," +
"\r\n\"identity_format\": \"raw\"\r\n}" +
"\r\n]," +
"\r\n\"property_id\": \"com.example.application\"}");
Request request = new Request.Builder()
.url("https://hq1.appsflyer.com/api/gdpr/v1/opendsr_requests/opendsr_requests")
.post(body)
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "application/json")
.addHeader("Authorization: Bearer", "")
.build();
try {
Response response = client.newCall(request).execute();
System.out.println(response.code());
System.out.println(response.body().string());
System.exit(0);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
}
""" using the requests python package,
install using pip install requests """
import requests
import json
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization: Bearer": ""
}
body = {
"subject_request_id": "",
"subject_request_type": "erasure",
"submitted_time": "2018-10-02T15:00:00Z",
"platform": "android",
"subject_identities": [
{ "identity_type": "android_advertising_id",
"identity_value": "",
"identity_format": "raw" }
],
"property_id": "com.example.application"
}
body = json.dumps(body)
res = requests.request("POST",
"https://hq1.appsflyer.com/api/gdpr/v1/opendsr_requests",
headers=headers, data=body, params=params)
print(res.text)
/* using the request npm package,
install using npm install request */
var request = require("request");
var options = { method: 'POST',
url: 'https://hq1.appsflyer.com/api/gdpr/v1/opendsr_requests',
headers:
{ Accept: 'application/json',
'Content-Type': 'application/json',
'Authorization: Bearer': '' },
body:
{ subject_request_id: '',
subject_request_type: 'erasure',
submitted_time: '2018-10-02T15:00:00Z',
platform: 'android',
subject_identities:
[ { identity_type: 'android_advertising_id',
identity_value: '',
identity_format: 'raw' } ],
property_id: 'com.example.application'
},
json: true };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
using System;
using RestSharp;
namespace CS
{
class Gdpr
{
static void Main(string[] args)
{
var url = "https://hq1.appsflyer.com/api/gdpr/v1/opendsr_requests";
var client = new RestClient(url);
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Accept", "application/json");
request.AddHeader("Authorization: Bearer", "")
var body = "{\r\n\"subject_request_id\": \"\"," +
"\r\n \"subject_request_type\": \"erasure\"," +
"\r\n \"submitted_time\": \"2018-11-02T15:00:00Z\"," +
"\r\n \"platform\": \"android\"," +
"\r\n \"subject_identities\": [\r\n" +
"{ \r\n \"identity_type\": \"android_advertising_id\"," +
"\r\n \"identity_value\": \"\"," +
"\r\n \"identity_format\": \"raw\"\r\n }" +
"\r\n ]," +
"\r\n \"property_id\": \"com.example.application\"}";
request.AddParameter("undefined", body,ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
// handle response by reading response.StatusCode
Console.WriteLine(response.Content);
}
}
2. 状态请求
每个已提交的GDPR请求都可以通过subject_request_id来查询其后续状态。支持的状态类型有四种:
- 待处理-已收到正确的请求, 并且正处于等待队列中
- 处理中-请求正在处理中
- 已完成-请求已完成
- 已取消-请求已被取消
状态请求格式
状态请求可以通过HTTP GET提交到以下端点:
https://hq1.appsflyer.com/api/gdpr/v1/opendsr_requests/<req_id>
状态响应示例
HTTP/1.1 200 OK
Content-Type: application/json
X-OpenGDPR-Processor Domain: example processor.com
X-OpenGDPR-Signature:
kiGlog3PdQx+FQmB8wYwFC1fekbJG7Dm9WdqgmXc9uKkFRSM4uPzylLi7j083461xLZ+mUloo3tpsmyIZpt5eMfgo7ejXPh6lqB4ZgCnN6+1b6Q3NoNcn/+11UOrvmDj772wvg6uIAFzsSVSjMQxRs8LAmHqFO4cF2pbuoPuK2diHOixxLj6+t97q0nZM7u3wmgkwF9EHIo3C6G1SI04/odvyY/VdMZgj3H1fLnz+X5rc42/wU4974u3iBrKgUnv0fcB4YB+L6Q3GsMbmYzuAbe0HpVA17ud/bVoyQZAkrW2yoSy1x4Ts6XKba6pLifIHf446Bubsf5r7x1kg6Eo7B8zur666NyWOYrglkOzU4IYO8ifJFRZZXazOgk7ggn9obEd78GBc3kjKKZdwaCrLx7WV5y9TMDCf+2FILOJM/MwTUy1dLZiaFHhGdzld2AjbjK1CfVzyPssch0iQYYtbR49GhumvkYl11S4oDfu0c3t/xUCZWg0hoR3XL3B7NjcrlrQinB1KbyTNZccKR0F4Lk9fDgwTVkrAg152UqPyzXxpdzXjfkDkSEgAevXQwVJWBNf18bMIEgdH2usF/XauQoyrne7rcMIWBISPgtBPj3mhcrwscjGVsxqJva8KCVCKD/4Axmo9DISib5/7A6uczJxQG2Bcrdj++vQqK2succ=
{
"controller_id":"example_controller_id",
"expected_completion_time":"2018-11-01T15:00:01Z",
"subject_request_id":"a7551968-d5d6-44b2-9831-815ac9017798",
"request_status":"pending",
}
状态回传
如上GDPR请求流程所述,当GDPR请求状态改变时,从待处理到处理中到完成,AppsFlyer会以status_callback_urls形式给GDPR请求端点回传状态信息。
状态回传示例:
POST /opengdpr_callbacks HTTP/1.1
Host: examplecontroller.com
Content-Type: application/json
X-OpenGDPR-Processor Domain: gdpr.appsflyer.com
X-OpenGDPR-Signature: kiGlog3PdQx+FQmB8wYwFC1fekbJG7Dm9WdqgmXc9uKkFRSM4uPzylLi7j083461xLZ+mUloo3tpsmyIZpt5eMfgo7ejXPh6lqB4ZgCnN6+1b6Q3NoNcn/+11UOrvmDj772wvg6uIAFzsSVSjMQxRs8LAmHqFO4cF2pbuoPuK2diHOixxLj6+t97q0nZM7u3wmgkwF9EHIo3C6G1SI04/odvyY/VdMZgj3H1fLnz+X5rc42/wU4974u3iBrKgUnv0fcB4YB+L6Q3GsMbmYzuAbe0HpVA17ud/bVoyQZAkrW2yoSy1x4Ts6XKba6pLifIHf446Bubsf5r7x1kg6Eo7B8zur666NyWOYrglkOzU4IYO8ifJFRZZXazOgk7ggn9obEd78GBc3kjKKZdwaCrLx7WV5y9TMDCf+2FILOJM/MwTUy1dLZiaFHhGdzld2AjbjK1CfVzyPssch0iQYYtbR49GhumvkYl11S4oDfu0c3t/xUCZWg0hoR3XL3B7NjcrlrQinB1KbyTNZccKR0F4Lk9fDgwTVkrAg152UqPyzXxpdzXjfkDkSEgAevXQwVJWBNf18bMIEgdH2usF/XauQoyrne7rcMIWBISPgtBPj3mhcrwscjGVsxqJva8KCVCKD/4Axmo9DISib5/7A6uczJxQG2Bcrdj++vQqK2succ=
{
"controller_id":"example controller id at the processor",
"expected_completion_time":"2018-11-01T15:00:01Z",
"status_callback_url":"https://examplecontroller.com/opengdpr_callbacks",
"Subject_request_id":"a7551968-d5d6-44b2-9831-815ac9017798",
"Request_status":"pending"
}
验证回传
要验证接收到的回传数据是否真实有效,请按以下步骤操作:
- 建立一个域名白名单,仅允许这些域名发起回调。
-
如果请求头中的
X-OpenGDPR-Processor-Domain值在白名单中,则获取其证书。-
证书URL位于
processor_certificate响应体中的/discovery字段。 -
您也可以直接从AppsFlyer端点获取证书,使用V2 API token作bearer token添加到Authorization请求头中:
GET https://hq1.appsflyer.com/api/gdpr/v1/certificate
-
证书URL位于
-
使用相关库验证证书,确保证书:
- 由受信任的机构签发;
-
签发对象与
X-OpenGDPR-Processor-Domain请求头中的值一致; - 未过期。
-
确认证书有效后,使用其验证
X-OpenGDPR-Signature请求头与原始请求体是否匹配。AppsFlyer使用SHA256 RSA作为签名算法。 -
检查响应状态:
-
202 Accepted:验证成功。 -
401 Unauthorized:签名验证失败,或处理方域名不在白名单中。
-
3. 报告请求
当访问请求或数据转移请求完成后, 您可以通过HTTP GET将报告下载到以下端点:
https://hq1.appsflyer.com/api/gdpr/v1/download/[REQUEST_ID]
生成的报告自完成之日起14天内有效。
4. Discovery请求-处理中
若要了解AppsFlyer支持的格式, 可以通过HTTP GET将发现请求提交到以下端点:
https://hq1.appsflyer.com/api/gdpr/v1/discovery
Discovery响应示例
HTTP/1.1 200 OK
Content-Type: application/json
{
"api_version": "0.1",
"supported_identities": [
{
"identity_type": "android_advertising_id",
"identity_format": "raw"
},
],
"supported_subject_request_types": [
"erasure", "access", "portability", "rectification"
],
"processor_certificate": "https://exampleprocessor.com/cert.pem"
}
5. 取消请求
用户可以基于subject_request_id取消GDPR请求,但仅能在待处理阶段取消请求。
如需提交包含subject_request_id的HTTP DELETE请求:
https://hq1.appsflyer.com/api/gdpr/v1/opendsr_requests/<req_id>
取消响应
当收到GDPR取消请求时, AppsFlyer会返回HTTP 202状态码及其他参数。
一旦请求被取消, AppsFlyer会发送状态为已取消的回传。
6. GDPR请求测试API
该AppsFlyer API是AppsFlyer GDPR请求API的测试API。
原理
测试 API 的工作原理如下:
1. GDPR请求提出后, 该请求将立即进入“待处理”状态。为达到测试目的,请求状态会每隔30秒自动变更一次。
2. 如果在GDPR请求中配置了状态回传的端点, 第一个回传会在请求提交后立即发送,随后每隔30秒再发送两次状态回传。
GDPR请求测试端点:POST
https://hq1.appsflyer.com/api/gdpr/v1/stub
状态请求测试端点:GET
https://hq1.appsflyer.com/api/gdpr/v1/stub/:requestId
发现请求测试端点:GET
https://hq1.appsflyer.com/api/gdpr/v1/stub/discovery
取消请求测试端点:删除
https://hq1.appsflyer.com/api/gdpr/v1/stub/:requestId
证书测试端点:GET
https://hq1.appsflyer.com/api/gdpr/v1/stubcertificate
访问/数据转移报告测试端点:GET
https://hq1.appsflyer.com/api/gdpr/v1/stub/download/:requestId
注意
- 请求头中必须包含有效的V2 API token,格式如下:
- 在property_id参数中填写的App ID必须属于该API token所对应的账号所有者。
7.请求日志
管理员用户可以在日志面板(Logs Dashboard) 中查看已提交的GDPR请求。
对于已完成的访问和数据转移请求, 您也可以从该控制面板中下载报表。
访问日志控制面板:
- 从页面顶部导航栏打开账户菜单(点击右上角的邮箱地址,打开下拉菜单)> E-GDPR日志。
- 系统将打开以下窗口:
8.GDPR API返回代码和错误信息
本节详细介绍了GDPR API的HTTP返回代码和错误消息。
GDPR API返回代码
| 返回码 | 说明 |
|---|---|
| 201 | Created(请求已创建) |
| 202 | Cancellation request received(取消请求已接收) |
| 400 | Bad request(错误请求)。响应体中会包含错误码与错误信息,见下表。 |
HTTP返回代码400 - 错误请求
当返回码为400时,返回的消息体会包含一个JSON,其中包含错误码和错误信息。
{ "error": { "code":400, "af_gdpr_code": "%AF error code%", "message":"%error message text%" } }
返回代码400错误请求消息
| 错误代码 | 错误描述(消息) |
|---|---|
| e211 | 无法取消状态无效的请求 |
| e212 | 请求不被允许。正在对标识符进行删除。 |
| e213 | 请求已存在 |
| e214 | 未找到请求 |
| e311 | 请求的内容类型无效 |
| e312 | API版本无效 |
| e313 | subject_request_id无效 |
| e314 | submitted_time格式无效 |
| e315 | status_callback_url长度无效 |
| e316 | status_callback_url格式无效 |
| e317 | app_id格式无效 |
| e318 | identity_type无效 |
| e319 | 应用平台与身份类型不匹配 |
| e321 | LAT用户不支持通过API提交 |
| e322 | subject_request_type无效 |
| e323 | subject_identities格式无效 |
| e324 | subject_identities长度无效 |
| e325 | subject_identities值无效 |
| e326 | 请求体JSON格式无效 |
| e411 | AppID错误或不属于您的账号 |
| e412 | 无权限取消删除请求 |
| e413 | 无权限查看请求 |
| e511 | 内部问题,请等待60分钟后重试。如问题仍存在,请联系AppsFlyer支持:support@appsflyer.com |
特点与局限性
- OpenDSR API请求速率限制:每分钟最多350个请求。超过该限制会导致报错,需要重新发送请求。在限制范围内,每天最多可执行约504000个请求。
- 若查询60天前的请求,将返回“request not found”(未找到请求)。