Skip to main content

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:

  1. 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.
  2. 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.
  3. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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 ts value from the header. This value is expressed as UNIX seconds (epoch seconds).
    • Your server may verify that the requestId has not been used before to prevent replay attacks.

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 NameDescription
keyIdThe 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.
methodThe HTTP method used for the postback request.
urlThe full URL of the postback request. This URL is encoded according to RFC3986.
requestIdA unique identifier for each postback request.
tsA timestamp to indicate when the postback request was made. This timestamp is expressed in UNIX seconds.
hmacThe 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 NameDescription
Fluent-Request-TimestampThis is the timestamp value that is used in the hashing calculation
Fluent-Request-KeyIdThe keyId of the hashing key
Fluent-Request-IdThe 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:

  1. Extract and Split the Header:

    • Each request contains a custom header Fluent-Request-Verifier with 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:
      keyId=signing_key_id, method=POST, url=https%3A%2F%2Fexample.com%2Fpath, requestId=12345, ts=1715871216
      and
      hmac=abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
  2. Prepare the Data for Hashing:

    • Use the part before the semicolon (;) as the data to be hashed.
  3. Compute the HMAC:

    • Extract the keyId, by either parsing the value from Fluent-Request-Verifier header, or use the value provided in the Fluent-Request-KeyId header
    • 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
  4. 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:

NameDescription
KeyId1001 - 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-VerifierkeyId=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
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")
}