At a glance: Add signature validation to clicks to avoid fraud liabilities and so fraudulent clicks aren't attributed to your ad network.
About click signing
With minimal tech, fraudsters can send clicks on behalf of an ad network and create thousands, or even millions, of fake clicks that get sent to AppsFlyer. Sometimes, the ad networks themselves aren't aware of the issue.
In order for an ad network to ensure that clicks attributed to it by AppsFlyer actually originated from the ad network, and not from a fraudster creating fake clicks, ad networks should sign their clicks with HMAC-SHA256 signatures.
The code adds the following
Click signing also prevents the ad network’s traffic from being blocked due to click flooding. Meaning, if an ad network reaches the click blocking threshold due to an extreme level of click flooding, AppsFlyer stops recording and attributing their clicks for the rest of the day.
The signatures enable AppsFlyer to validate the clicks and make sure that the click information hasn't been manipulated by fraudsters.
- Validated clicks are recorded, and attributed to the ad network
-
Invalidated clicks are rejected and:
- Are made available in Protect360 reports for ad networks (not advertisers). Learn more
- Do not impact the conversion rate or click blocking threshold of the ad network
Click signature integration
Flow
The following chart outlines the flow from initial development and basic test, to production tests, and finally, to production.
Procedure
Prerequisite: API V2.0 token from the admin to authorize the click signing API.
To sign your clicks:
-
Generate a secret key with the Generate secret key API.
Best practice: Generate and use a new secret key every 24 hours, with an expiration of 36 hours. -
Develop code in your servers that calls the Generate secret key API, takes the secret key and generates an HMAC-SHA256 signature. See code sample.
You can use the other APIs as described in the table that follows. - The code adds the following to your click URLs:
- An expires parameter that contains a Unix timestamp (in UTC) after which the ad network doesn't claim the click.
- The HMAC-SHA256 signature.
- Example:
https://app.appsflyer.com/com.app.id?pid=adnetwork_int&c=my_campaign&clickid=sdkfjasksjskdfj9845weh&af_site_id=12345&expires=1597657118&signature=8fnDVzZP_WRZnv3KNJaREOEfvB5p9oRc_XlKEvUo8gk
Note
Make sure any URL encoding of special characters or spaces in your link occurs before the click signature is generated. Generating the signature first results in verification failure.
Click signature APIs
AppsFlyer provides APIs that allow ad networks to manage and test the click signing process. See the list of APIs in the table below, and the sections that follow with information necessary for using the API.
Click signature APIs
API method | Remarks |
---|---|
Generate secret key | Generate secret keys to be used in the signature. |
Revoke secret key | Cancel compromised secret keys. |
Test | Send single clicks for testing the signature. |
Configure mode |
Configure mode of click signing mode:
|
Configure circuit breaker |
Configure the mode of the circuit breaker that protects the ad network from having too many blocked clicks:
|
Get configuration |
Get the mode and IDs of active secret keys. |
Report |
Get statistics on successful and failing clicks when the system is in report-only or enabled mode. Use this to test click signing without impacting production and real traffic. |
Exclude app | Configure app IDs to be excluded from click signing. |
Remove excluded app | Configure app IDs to be included in click signing after being excluded. |
Generate secret key method
Generate secret key basics
Category | Item |
Description |
---|---|---|
Request | HTTP method | POST |
Path |
https://hq1.appsflyer.com/api/p360-click-signing/secret?ttlHours=<ttlHours> |
|
Authorization header |
|
|
Response | Results | Secret key returns in a JSON |
Request limit | Maximum of 2 active secret keys at a time |
API request
Method
POST https://hq1.appsflyer.com/api/p360-click-signing/secret?ttlHours=<ttlHours>
Parameters
Parameter |
Description |
---|---|
ttlHours |
|
JSON response
Key |
Description |
---|---|
secret-key-id |
An ID for the secret key |
secret key |
The secret key for the click signature |
expiration |
Epoch time in milliseconds |
Generate secret key curl example and response
Curl request
curl --location --request POST 'https://hq1.appsflyer.com/api/p360-click-signing/secret?ttlHours=36' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}'
JSON response
{
"secret-key-id": "59ad6547-affc-45eb-a6c9-9805f88ee755",
"secret-key": "zGW6Rhrmb8+vuhHtL/Kp6rW5Ci9PNsjH1J5MGO9SIeg=",
"expiration": 1610533263
}
HTTP response codes
Response codes
Code |
Message |
Remarks |
---|---|---|
200 | OK |
|
401 | Not authorized |
Invalid or missing authorization header |
Revoke secret key method
Revoke secrete key basics
Category | Item |
Description |
---|---|---|
Request | HTTP method | DELETE |
Path |
https://hq1.appsflyer.com/api/p360-click-signing/secret/<secret-id> |
|
Authorization header |
|
|
Response | Results | Empty |
API request
Method
DELETE https://hq1.appsflyer.com/api/p360-click-signing/secret/<secret-id>
Parameters
Parameter |
Description |
---|---|
secret-id |
The ID of the secret key to be revoked |
Generate secret key curl example and response
Curl request
curl --location --request DELETE 'https://hq1.appsflyer.com/api/p360-click-signing/secret/59ad6547-affc-45eb-a6c9-9805f88ee755' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}'
HTTP response codes
Response codes
Code |
Message |
Remarks |
---|---|---|
200 | OK |
|
401 | Not authorized |
Invalid or missing authorization header |
Test method
Test basics
Category | Item |
Description |
---|---|---|
Request | HTTP method | POST |
Path |
https://hq1.appsflyer.com/api/p360-click-signing/test |
|
Authorization header |
|
|
Response | Results | Returns in a JSON |
API request
Method
POST https://hq1.appsflyer.com/api/p360-click-signing/test
Parameters
Parameter |
Description |
---|---|
url |
The click URL (including signature) to test |
JSON response
Key |
Description |
---|---|
test-status |
Either Passed or Failed |
message |
Reason for the test failure. For example:
|
Test curl example and response
Curl request
curl --location --request POST 'https://hq1.appsflyer.com/api/p360-click-signing/test' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://app.appsflyer.com/com.app.id?pid=adnetwork_int&c=my_campaign&clickid=sdkfjasksjskdfj9845weh&af_site_id=12345&expires=1597657118&signature=8fnDVzZP_WRZnv3KNJaREOEfvB5p9oRc_XlKEvUo8gk"
}'
JSON response
{
"test-status":"Passed / Failed",
"message": "Invalid signature"
}
HTTP response codes
Response codes
Code |
Message |
Remarks |
---|---|---|
200 | OK |
|
401 | Not authorized |
Invalid or missing authorization header |
Configure mode method
Configure mode basics
Category | Item |
Description |
---|---|---|
Request | HTTP method | POST |
Path |
https://hq1.appsflyer.com/api/p360-click-signing/config/mode/<mode> |
|
Authorization header |
|
|
Response | Results | Returns in a JSON |
API request
Method
POST https://hq1.appsflyer.com/api/p360-click-signing/config/mode/<mode>
Parameters
Parameter |
Description |
---|---|
mode |
Options:
|
Configure mode curl example
Curl request
curl --location --request POST 'https://hq1.appsflyer.com/api/p360-click-signing/config/mode/report-only' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}'
HTTP response codes
Response codes
Code |
Message |
Remarks |
---|---|---|
200 | OK |
|
400 | Bad request |
Invalid mode |
401 | Not authorized |
Invalid or missing authorization header |
Configure circuit breaker method
Configure circuit breaker basics
Category | Item |
Description |
---|---|---|
Request | HTTP method | POST |
Path |
https://hq1.appsflyer.com/p360-click-signing/config/circuit-breaker |
|
Authorization header |
|
|
Response | Results | HTTP status |
API request
Method
POST https://hq1.appsflyer.com/p360-click-signing/config/circuit-breaker
Request body JSON
Parameter |
Description |
---|---|
status |
|
Configure circuit breaker curl example and response
Curl request
curl --location --request POST 'https://hq1.appsflyer.com/api/p360-click-signing/config/circuit-breaker' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}'
--data-raw '{
"status":"enabled"
}'
HTTP response codes
Response codes
Code |
Message |
Remarks |
---|---|---|
200 | OK |
|
400 | Bad request |
Invalid status |
401 | Not authorized |
Invalid or missing authorization header |
Get configuration method
Get configuration basics
Category | Item |
Description |
---|---|---|
Request | HTTP method | GET |
Path |
https://hq1.appsflyer.com/api/p360-click-signing/config |
|
Authorization header |
|
|
Response | Results | Returns in a JSON |
API request
Method
GET https://hq1.appsflyer.com/api/p360-click-signing/config
JSON response
Key |
Description |
---|---|
mode |
One of:
|
circuit-breaker-config |
A JSON object containing status, one of:
|
active-key-ids |
A JSON array holding active keys:
|
excluded-app-ids |
A JSON array with excluded app-ids |
Get configuration curl example and response
Curl request
curl --location --request GET 'https://hq1.appsflyer.com/api/p360-click-signing/config' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}'
JSON response
{
"mode": "report-only",
"active-key-ids": [
{
"secret-key-id": "59ad6547-affc-45eb-a6c9-9805f88ee755",
"expiration": 1610533263
}
],
"excluded-app-ids": [
"app-id-1", "app-id-2"
]
}
HTTP response codes
Response codes
Code |
Message |
Remarks |
---|---|---|
200 | OK |
|
401 | Not authorized |
Invalid or missing authorization header |
Report method
Report basics
Category | Item |
Description |
---|---|---|
Request | HTTP method | GET |
Path |
https://hq1.appsflyer.com/api/p360-click-signing/report |
|
Authorization header |
|
|
Response | Results | Returns in a CSV |
API request
Method
GET https://hq1.appsflyer.com/api/p360-click-signing/report
Parameters
Parameter |
Description |
---|---|
start-date |
Start date and time for the report. Format: yyyy-mm-ddThh |
end-date |
End date and time for the report. Format: yyyy-mm-ddThh |
The API requires either both start-date and end-date or neither of them. If start/end date are not provided, the report shows results for the past 24 hours. |
CSV response
Column |
Description |
---|---|
time |
Date and time of the clicks. Format yyyy-mm-ddThh |
total_clicks |
Total number of clicks during the reporting period |
valid_clicks |
Number of valid clicks during the reporting period |
missing_signature |
Number of clicks missing signatures during the reporting period |
expired_clicks |
Number of expired clicks during the reporting period |
invalid_signature |
Number of clicks with invalid signatured during the reporting period |
no_active_secrets |
Number of clicks rejected because there are no active secret keys in the system (usually when the system is in report-only mode) |
Test curl example and response
Curl request
curl --location --request GET 'https://hq1.appsflyer.com/api/p360-click-signing/report?start-date=2021-01-07T07&end-date=2021-01-17T12' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}' \
CSV response
time |
total_clicks |
valid_clicks |
missing_signature |
expired_clicks |
invalid_signature |
no_active_signatures |
---|---|---|---|---|---|---|
2021-01-17T07 |
928082156 |
928082156 |
0 |
0 |
0 |
0 |
2021-01-17T08 |
923796132 |
923796132 |
0 |
0 |
0 |
0 |
2021-01-17T09 |
917541373 |
917541373 |
0 |
0 |
0 |
0 |
2021-01-17T10 |
909977064 |
909977064 |
0 |
0 |
0 |
0 |
2021-01-17T11 |
965104299 |
965104299 |
0 |
0 |
0 |
0 |
2021-01-17T12 |
975134824 |
975134824 |
0 |
0 |
0 |
0 |
HTTP response codes
Response codes
Code |
Message |
Remarks |
---|---|---|
200 | OK |
|
401 | Not authorized |
Invalid or missing authorization header |
Exclude app method
Exclude app method basics
Category | Item |
Description |
---|---|---|
Request | HTTP method | POST |
Path |
https://hq1.appsflyer.com/api/p360-click-signing/config/excluded-app/<app-id> |
|
Authorization header |
|
|
Response | Results | Empty |
API request
Method
POST https://hq1.appsflyer.com/api/p360-click-signing/config/excluded-app/<app-id>
Parameters
Parameter |
Description |
---|---|
app-id |
Application ID to be excluded from click signing validation |
Exclude app curl example
Curl request
curl --location --request POST 'https://hq1.appsflyer.com/api/p360-click-signing/config/excluded-app/appname.com' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}'
HTTP response codes
Response codes
Code |
Message |
Remarks |
---|---|---|
200 | OK |
|
401 | Not authorized |
Invalid or missing authorization header |
Remove excluded app method
Remove excluded app method basics
Category | Item |
Description |
---|---|---|
Request | HTTP method | DELETE |
Path |
https://hq1.appsflyer.com/api/p360-click-signing/config/excluded-app/<app-id> |
|
Authorization header |
|
|
Response | Results | Empty |
API request
Method
DELETE https://hq1.appsflyer.com/api/p360-click-signing/config/excluded-app/<app-id>
Parameters
Parameter |
Description |
---|---|
app-id |
Application ID to be removed from the list of apps excluded from click signing validation |
Remove excluded app curl example
Curl request
curl --location --request DELETE 'https://hq1.appsflyer.com/api/p360-click-signing/config/excluded-app/appname.com' \
-H 'Authorization: Bearer {API V2.0 token available to the admin in the dashboard.}'
HTTP response codes
Response codes
Code |
Message |
Remarks |
---|---|---|
200 | OK |
|
401 | Not authorized |
Invalid or missing authorization header |
Code sample
package sign;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
.
.
.
String clickUrl = "https://app.appsflyer.com/com.app.id?pid=adnetwork_int&c=my_campaign&clickid=sdkfjasksjskdfj9845weh&af_site_id=12345";
String secretKey = "secret_key";
int ttlMinutes = 5;
//add expiration to the click URL
long expiration = System.currentTimeMillis() + (60000L * ttlMinutes);
clickUrl += "&expires="+expiration;
//create a SecretKey object from the given key string
SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
//generate a signature from the click url and encode it with base64 without padding
String generatedSignature =
Base64.getUrlEncoder().withoutPadding().encodeToString(mac.doFinal(clickUrl.getBytes()));
//add the signature to the click URL
String signedClickUrl = clickUrl + "&signature=" + generatedSignature;
Additional information
Troubleshooting
AppsFlyer stops click signature validation and reverts to the report-only mode when more than 90% of clicks in an hour fail signature validation.
This is to protect your business from a potential technical issue and allows you to find the cause of the anomaly:
- If you find that the signature is working as expected and the clicks are being correctly blocked, disable the circuit-breaker using the configure circuit breaker method.
-
If you find that clicks are being incorrectly blocked:
- Make sure that you have a valid secret key by checking the click signing configuration using the get configuration method.
- Use the click signing report to get more information about the blocked clicks and investigate the sources (agency/app- iDs) and reasons for invalid clicks.
- If you find a problem with a specific app because of a non-standard integration, exclude this app from click signing validation using the exclude app API.
-
If you find a problem with your configuration:
- Continue running in report-only mode.
- Fix the click signing process on your side.
- Check the results in the click signing report and re-enable click-signing validation when you see that clicks are being validated as expected.
FAQ
Q: How can we test click signing without impacting production? A: There are two ways to test click signing:
|
Q: What is the difference between an API token and a secret-key? A: API token: Is used to authorize and run the click signing API. There is only one per ad network. The AppsFlyer API V2.0 token must be obtained from the admin Secret key: Is used to generate the signature. Use the generate secret key method to create secret keys. The ad network is responsible for generating new secret keys. See the traits section for more information. |
Q: Can we apply click signing to only certain campaigns? A: No. Click signing is applied to all clicks from an ad network. You can exclude certain apps from click signing, but you can’t exclude only certain campaigns. |
Traits
Trait |
Description |
---|---|
Click signature | The signature must occur on the ad network servers. |
Secret key |
|
Report API | Updated statistics of click validity are aggregated on an hourly basis. |