Postbacks
A postback is a server-to-server communication method used to notify external systems about specific events or actions that have occurred. In the context of our advertising platform, a postback is a mechanism that sends key event data, such as conversions, clicks, or other significant interactions, from our server to the publisher's server in real-time.
Securing Postback Requests
To enhance the security and integrity of our API communications, we are incorporating HMAC (Hash-based Message Authentication Code) into our postback requests. Here are the key reasons for this addition:
Authentication:
- HMAC provides a mechanism to authenticate the sender of the request. Since the HMAC is generated using a shared secret key known only to the client and the server, it verifies that the request indeed comes from a trusted source.
Replay Attack Prevention:
- Including a unique identifier (in our case the
requestId) and a timestamp in the HMAC computation prevents replay attacks. This ensures that each request is unique and can only be used once within a specified time frame.
- Including a unique identifier (in our case the
Enhanced Security:
- HMAC uses a cryptographic hash function along with a secret key to generate a signature for the message. This makes it computationally infeasible for attackers to forge valid HMAC signatures without knowing the secret key.
How It Works
Generate a Unique Request Signature:
- Each request includes a unique identifier and a timestamp.
- Our server constructs a string that includes the HTTP method, the URL (with parameters), the unique identifier, and the timestamp.
Compute the HMAC:
- Our server uses a shared secret key to compute the HMAC of the constructed string using a hash function (HMACSHA256).
- This HMAC is included in the request headers.
Server Verification:
- Upon receiving the request, your server uses the same shared secret key to recompute the HMAC using the received data.
- Your server compares the computed HMAC with the HMAC included in the request. If they match, the request is authenticated and considered intact.
Timestamp and RequestId Validation (Optional):
- Your server may verify that the the timestamp is within an acceptable time window. You may do this by parsing out the
tsvalue from the header. This value is expressed as UNIX seconds (epoch seconds). - Your server may verify that the
requestIdhas not been used before to prevent replay attacks.
- Your server may verify that the the timestamp is within an acceptable time window. You may do this by parsing out the
Example Header Format
Fluent-Request-Verifier: keyId=signing_key_id, method=POST, url=https%3A%2F%2Fexample.com%2Fpath, requestId=12345, ts=1715871216;hmac=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
| Paramter Name | Description |
|---|---|
| keyId | The identifier of the signing key. When this key is rotated, we'll reach out to you ahead of time with your new signing key and it's id. You can parse this parameter out and then lookup the appropriate shared key in your system to verify the hash. We will start signing requests with the new key once your team has confirmed that you're ready. |
| method | The HTTP method used for the postback request. |
| url | The full URL of the postback request. This URL is encoded according to RFC3986. |
| requestId | A unique identifier for each postback request. |
| ts | A timestamp to indicate when the postback request was made. This timestamp is expressed in UNIX seconds. |
| hmac | The SHA256 hash representation of the data to the left of the delimiter (;). This hash value is expressed as a hex string. |
Additional Headers Supplied with Postback Request:
The following headers contian the values that are used in the hashing calculation; they are made available as headers to simplify the hash verification process.
| Header Name | Description |
|---|---|
| Fluent-Request-Timestamp | This is the timestamp value that is used in the hashing calculation |
| Fluent-Request-KeyId | The keyId of the hashing key |
| Fluent-Request-Id | The Id of the request |
Verifying HMAC for API Requests
Here's how you, as our partner, can verify the HMAC included in the request headers:
Extract and Split the Header:
- Each request contains a custom header
Fluent-Request-Verifierwith the following format:keyId=signing_key_id, method=POST, url=https%3A%2F%2Fexample.com%2Fpath, requestId=12345, ts=1715871216;hmac=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 - Split the header value on the semicolon (
;). This separates the data to be hashed from the HMAC:andkeyId=signing_key_id, method=POST, url=https%3A%2F%2Fexample.com%2Fpath, requestId=12345, ts=1715871216hmac=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
- Each request contains a custom header
Prepare the Data for Hashing:
- Use the part before the semicolon (
;) as the data to be hashed.
- Use the part before the semicolon (
Compute the HMAC:
- Extract the keyId, by either parsing the value from
Fluent-Request-Verifierheader, or use the value provided in theFluent-Request-KeyIdheader - Lookup the key by it's id in your system (you will need to store/lookup these keys)
- Use the HMACSHA256 algorithm to compute the hash of the data using the shared secret
- Extract the keyId, by either parsing the value from
Compare the HMAC Values:
- Convert the computed HMAC to a hex string.
- Compare the computed HMAC with the HMAC value extracted from the header.
- If they match, the request is verified.
Example Steps in Code
For each example we'll be using the following set of parameters:
| Name | Description |
|---|---|
| KeyId | 1001 - Note: This is irrelevant in the examples as we're not rotating the key or implementing a key lookup service. |
| Key (hex string) | e6f6e1ef6108a62b0f50441e4a59fdb994dfe6474c286581e82d8d83625ac834 |
| Fluent-Request-Verifier | keyId=1001, method=GET, encoded_url=https%3A%2F%2Fexample.com%2Fconversion%3Ffoo%3Dbar%26payout%3D1200, requestId=ade66196-6d25-415d-89f5-7ced27e92617, ts=1715941726;hmac=1cccdd27bb77bb7da18d77df12bbb3c7c851c389b12581ecda224c17a9d69fe1 |
- NodeJS
- Python
- C#
const crypto = require('crypto');
function verifyHmac(headerValue, hexKey) {
// Convert the hex key to bytes
const sharedKey = Buffer.from(hexKey, 'hex');
// Split the header value on the semicolon
const [data, hmac] = headerValue.split(';hmac=');
// Compute the HMAC
const computedHmac = crypto.createHmac('sha256', sharedKey)
.update(data)
.digest('hex');
// Compare the computed HMAC with the provided HMAC
return computedHmac === hmac;
}
// Example usage
const headerValue = 'keyId=1001, method=GET, encoded_url=https%3A%2F%2Fexample.com%2Fconversion%3Ffoo%3Dbar%26payout%3D1200, requestId=ade66196-6d25-415d-89f5-7ced27e92617, ts=1715941726;hmac=1cccdd27bb77bb7da18d77df12bbb3c7c851c389b12581ecda224c17a9d69fe1';
//This is where you would lookup the hashing key in your system by the keyId
// You may parse the keyId out of the Fluent-Request-Verifier header OR you can use the value from the Fluent-Request-KeyId header
const hexKey = 'e6f6e1ef6108a62b0f50441e4a59fdb994dfe6474c286581e82d8d83625ac834';
if(verifyHmac(headerValue, hexKey)) {
console.log('Hash Verified')
}
else {
console.log("Hash not verified")
}
import hmac
import hashlib
import time
def verify_hmac(header_value, hex_key):
# Convert the hex key to bytes
shared_key = bytes.fromhex(hex_key)
# Split the header value on the semicolon
data, hmac_value = header_value.split(';hmac=')
# Compute the HMAC
computed_hmac = hmac.new(shared_key, data.encode(), hashlib.sha256).hexdigest()
# Compare the computed HMAC with the provided HMAC
if computed_hmac != hmac_value:
return False
return True
# Example usage
header_value = 'keyId=1001, method=GET, encoded_url=https%3A%2F%2Fexample.com%2Fconversion%3Ffoo%3Dbar%26payout%3D1200, requestId=ade66196-6d25-415d-89f5-7ced27e92617, ts=1715941726;hmac=1cccdd27bb77bb7da18d77df12bbb3c7c851c389b12581ecda224c17a9d69fe1'
# This is where you would lookup the hashing key in your system by the keyId
# You may parse the keyId out of the Fluent-Request-Verifier header OR you can use the value from the Fluent-Request-KeyId header
hex_key = 'e6f6e1ef6108a62b0f50441e4a59fdb994dfe6474c286581e82d8d83625ac834'
if verify_hmac(header_value, hex_key):
print("Hash verified")
else:
print("Has not verified")
using System;
using System.Security.Cryptography;
using System.Text;
public class Program
{
public static byte[] HexToBytes(string hex)
{
int numberChars = hex.Length;
byte[] bytes = new byte[numberChars / 2];
for (int i = 0; i < numberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
public static bool VerifyHmac(string headerValue, string hexKey)
{
// Convert the hex key to bytes
byte[] sharedKey = HexToBytes(hexKey);
// Split the header value on the semicolon
var parts = headerValue.Split(new[] { ";hmac=" }, StringSplitOptions.None);
string data = parts[0];
string hmacValue = parts[1];
// Compute the HMAC
using (var hmac = new HMACSHA256(sharedKey))
{
byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
string computedHmac = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
// Compare the computed HMAC with the provided HMAC
return computedHmac == hmacValue;
}
}
public static void Main()
{
string headerValue = "keyId=1001, method=GET, encoded_url=https%3A%2F%2Fexample.com%2Fconversion%3Ffoo%3Dbar%26payout%3D1200, requestId=ade66196-6d25-415d-89f5-7ced27e92617, ts=1715941726;hmac=1cccdd27bb77bb7da18d77df12bbb3c7c851c389b12581ecda224c17a9d69fe1";
//This is where you would lookup the hashing key in your system by the keyId
//You may parse the keyId out of the Fluent-Request-Verifier header OR you can use the value from the Fluent-Request-KeyId header
string hexKey = "e6f6e1ef6108a62b0f50441e4a59fdb994dfe6474c286581e82d8d83625ac834";
if(VerifyHmac(headerValue, hexKey))
Console.WriteLine("Hash verified");
else
Console.WriteLine("Hash not verified");
}
}