API signing

Antavo APIs use different security mechanisms to protect data integrity and authenticate client requests. Depending on the API being accessed, requests may require either request signing or token-based authentication.

This document describes the API request signing mechanism used by Antavo.

📘

Note

For APIs that use token-based authentication, refer to the Authentication endpoint document.

Antavo enhances HTTPS security by requiring every request to be signed with an API signature, ensuring data transmission integrity, and detecting any in-transit changes. The Antavo Loyalty Cloud uses Escher, a stateless HTTP request signing specification based on an enhanced version of the AWS Signature Version 4 protocol.

To sign a request using Escher authentication, you need to create a hash (digest) of the request and use it, along with other meta data and your API secret, to generate the request signature (a signed hash). This signature must then be added to the request in the HTTP Authorization header. This document describes the process of creating the signature and signing the request, and provides clear examples.

Open-source libraries such as EscherPHP, EscherJava , EscherC#, and EscherAuth in Go are available for integration into your system. You can find examples for signing GET and POST requests using Antavo's EscherPHP library in the Signing requests using PHP section.

For implementing Escher authentication in other programming languages, see the list of Escher open-source libraries which also includes installation guides and usage examples.

📘

Note

Accessing information from APIs in production environments requires a signed request. A pre-request script for generating an API signature is available in the Antavo APIs Postman collection on the collection-level. A second pre-request script for signing the request with the generated signature is also included on the request-level. Note that the pre-request scripts depend on the environment variables provided with the Postman collection.

In case you want to set up the Antavo API signature in Postman for a different collection, please refer to the Signing API requests in Postman document.

The signature generation protocol

The process of creating an API signature and using it to sign an API request to Antavo consists of 4 major steps:

  1. Create a Canonical Request string.
  2. Create a String to Sign using the hashed Canonical Request and additional meta information.
  3. Create the Request Signature by signing the String to Sign with a Signing Key derived from the Antavo API Secret.
  4. Add the Request Signature to the Authorization header of the HTTP request.

When Antavo receives the request, it performs the same calculation on the server side. The signature calculated by the server is then compared to the signature submitted in the request's Authorization header. If the two signatures do not match, the API request is denied.

All URL paths should be normalized according to the RFC3986 standard. Failure to follow these steps will result in the creation of an invalid signature, and all signed requests will be automatically rejected.

🚧

Note

API signatures should not be generated on front-end implementations as this could potentially expose API keys and secrets.

1. Create the Canonical Request string

Antavo implements a standardized (canonical) request format where the request constituents are concatenated withLF (line feed, \n) characters into a string. Each element in the request consists of one row of data, except for the CanonicalHeaders element which consists of one row of data per canonical header.

🚧

Note

An additional LF character must be inserted after the last canonical header, so that there is an empty line between the last canonical header and the signed headers.

HTTPRequestMethod
CanonicalURI
CanonicalQueryString
CanonicalHeaders

SignedHeaders
HashedRequestPayload

HTTPRequestMethod

The HTTP request method, defined by RFC2616, is case sensitive and must be given in uppercase.

E.g., GET

CanonicalURI

The CanonicalURI is the URI-encoded absolute path component of the URI of the endpoint – the part of the URI that is between the hostname and the query string (if any). It starts with a forward slash (/) character, and does not include the query string separator (?).

Each path component, including the path variables, must be URI-encoded according to the rules defined by RFC3986:

  • Extended UTF-8 characters must be percent-encoded in the form %XY%ZA (where X, Y, Z and A are hexadecimal characters:0-9 and uppercase A-F). E.g., á is encoded to %C3%A1.
  • Spaces must be encoded to %20 (and not to +).

E.g., /customers/example-customer-id (where example-customer-id is the value of a path variable)

CanonicalQueryString

The query string in a URL starts after the query string separator (?), and consists of the query parameters and their associated values. To build the CanonicalQueryString:

  1. Sort the query parameter names by character code point in ascending order with uppercase letters preceding lowercase ones.
  2. URI-encode the parameter names and their values according to the rules defined by RFC3986:
    • Do not URI-encode unreserved characters (as defined by RFC3986): A-Z, a-z, 0-9, hyphen (-), underscore (_), period (.), and tilde (~).
    • Percent-encode all other characters in the form %XY (where X and Y are hexadecimal characters:0-9 and uppercase A-F). Extended UTF-8 characters must be in the form %XY%ZA (e.g., á is encoded to %C3%A1).
    • Spaces must be encoded to %20 (and not to +), and commas must be encoded as %2C.
🚧

Note

If the Antavo PHP SDK is not used for the API signature implementation, the + character and its percent-encoded version (%2B) in the CanonicalQueryString must be replaced with %20 during the API signature generation and calculation to ensure the signature is valid.

  1. Build the canonical query string:
    • Order the parameters and their values starting with the first parameter name in the sorted list.
    • Separate the URI-encoded parameter names and their URI-encoded values using the equals sign (=). For parameters with an empty value, include the parameter name and an equals sign (=) followed by an empty string, e.g., max_price=125&min_price=.
    • Separate parameters using an ampersand (&).

E.g.: max_price=125&min_price=50

🚧

Note

If the request does not include query parameters, the CanonicalQueryString must be normalized to an empty string, and an empty line ( LF) must be inserted between the CanonicalURI and the CanonicalHeaders.

CanonicalHeaders

The CanonicalHeaders part of the Canonical Request is a sorted list of the canonical forms of all HTTP headers included in the request. The Host and Date HTTP headers are required for each request; other standard headers such as Content-Type are optional.

To create the CanonicalHeaders list:

  1. Convert each header name-value pair in the request to lowercase and remove leading and trailing spaces. Convert sequential spaces in the header value to a single space.
  2. Sort the header names by character code point in ascending order.
  3. Build the CanonicalHeaders list by adding the sorted, lowercase header name-value pairs in the following format. Note that an LF character must be inserted following each canonical header.
header-name-a:header value A
header-name-b:header value b
header-name-c:header value c1,header value c2
📘

Note

If a header has multiple values, they must be added as a non-sorted comma-separated list of values for its respective canonical header.

Below you can find an example of a set of HTTP headers and their respective CanonicalHeaders list:

Host:api.staging.antavo.com
Content-Type:application/x-www-form-urlencoded; charset=utf-8
My-Header1:    a   b   c  
Date:20241121T142143Z
My-Header2:    "a   b   c"  
content-type:application/x-www-form-urlencoded; charset=utf-8
date:20241121T142143Z
host:api.staging.antavo.com
my-header1:a b c
my-header2:"a b c"
🚧

Note

An additional LF character must be inserted after the last canonical header, so that there is an empty line between the last canonical header and the signed headers.

SignedHeaders

The SignedHeaders part of the Canonical Request is a single-line, lowercase, sorted list of headers separated by a semicolon. The list shows which headers are to be included when calculating the API signature, and is based on the canonical headers derived in the previous step. Note that the date and host headers are required. Other headers such as content-type are optional.

📘

Note

The Authorization header is not included in the list of signed headers as its content is created after the Request Signature is calculated.

The header names in the list must be:

  • lowercase,
  • sorted by character code point in ascending order,
  • separated by a semicolon (;).

E.g., content-type;date;host

HashedRequestPayload

In this part of the Canonical Request, the SHA-256 algorithm is used to generate a string: a lowercase, Base16-encoded hashed value of the request payload (adhering to Section 8 of RFC4648).

Note that the same algorithm must be used when creating the String to Sign and throughout the entire API signing process. This algorithm is also added to the Authorization header, so the server uses the same algorithm for validation. Certain Antavo services may require a specific encoding, and detailed information will be provided in their respective documentation.

🚧

Note

For requests with an empty payload, an empty string must be used as the input for the hashing algorithm.

E.g., e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Constructing the Canonical Request string

The Canonical Request string is constructed by concatenating its constituent parts into a string using LF characters. Note that each part of the Canonical Request produces a row of data with the exception of the CanonicalHeaders element which produces one row of data per canonical header.

When constructing the Canonical Request string, the order of the elements must be the following:

HTTPRequestMethod
CanonicalURI
CanonicalQueryString
CanonicalHeaders
  
SignedHeaders
HashedRequestPayload
📘

Note

  • If the request does not include query parameters, an LF character must be inserted in place of the CanonicalQueryString, resulting in twoLF characters between the CanonicalURI and the CanonicalHeaders.
  • As each canonical header is followed by an LF character, including the last one, and the CanonicalHeaders and SignedHeaders parts are concatenated with an LF character, this results in twoLF characters between the last canonical header and the list of signed headers.

2. Create the String to Sign

The String to Sign is a combination of the hashed Canonical Request and additional meta information about the request (the used hashing algorithm and the request's date, time, and credential scope). Together, these elements form the String to Sign, which is then used along with a derived Signing Key to compute the Request Signature in Step 3.

The String to Sign is constructed by concatenating the hashing algorithm, the request's date and time, the credential scope of the request, and the hashed Canonical Request using LF characters:

HashingAlgorithm
RequestDateTime
CredentialScope
HashedCanonicalRequest

HashingAlgorithm

The hashing algorithm is used throughout the API signing process. Currently, only the ANTAVO-HMAC-SHA256 standard SHA-256 hashing algorithm is supported.

RequestDateTime

The date and time of the request in the UTC timezone as specified in the date header. The long date must be provided in the following ISO 8601 basic format: YYYYMMDDTHHMMSSZ where:

  • YYYY = four-digit year
  • MM = two-digit month (01=January, etc.)
  • DD = two-digit day of the month (01 through 31)
  • T appears literally in the string to indicate the beginning of the time element, as specified in ISO 8601.
  • HH = two digits of the hour (00 through 23)
  • MM = two digits of the minute (00 through 59)
  • SS = two digits of the second (00 through 59)
  • Z appears literally in the string; Z is the time zone designator for the UTC timezone, as specified in ISO 8601.

E.g., 20241121T142143Zfor a request made on November 21, 2024 at 14h 21m 43s UTC

🚧

Note

  • The date provided in the RequestDateTime element must be consistent with the rest of the date values used during the API signing process.
  • For security reasons, the date header is only accepted if it is within ±5 minutes of the actual time. Requests outside this range will not be processed.

CredentialScope

A service identifier string formatted in the following way:

{date}/{environment}/api/antavo_request

Where:

  • {date}: The date of the request, provided in the YYYYMMDD short date format (note that it does not include a time value) where:

    • YYYY = four-digit year
    • MM = two-digit month (01=January, etc.)
    • DD = two-digit day of the month (01 through 31)
  • {environment}: Corresponds to the target environment, which can be found on the API settings page of the Management UI next to Your API Region.

  • api/antavo_request: Specifies the requested service.

E.g., 20241121/staging/api/antavo_request

HashedCanonicalRequest

This is the lowercase Base16-encoded hashed Canonical Request. The digest (hash) of the Canonical Request must be computed using the same algorithm that was used to hash the request payload in Step 1.

E.g., a76347a06493c9e40ed997972fa782a30408bc0d8cb943f4b5164166f53c3a19

Constructing the String to Sign

The String to Sign is constructed by concatenating the hashing algorithm, the request's date and time, the credential scope of the request, and the hashed Canonical Request using LF characters. The order of the elements must be the following:

HashingAlgorithm
RequestDateTime
CredentialScope
HashedCanonicalRequest

3. Create the Request Signature

To generate a secure Request Signature, a Signing Key derived from the Antavo API Secret is required. This key is specific to the date, service, and environment of the request, enhancing security by limiting the key's scope.

The Signing Key and the String to Sign (described in Step 2) are used as the input values in a keyed hash function (HMAC). The hex-encoded result from the keyed hash function is the Request Signature.

Generate the Signing Key

To generate the Signing Key, the Antavo API Secret undergoes a series of HMAC (Hash-based Message Authentication Codes) operations which follow the RFC2104 standard:

SecretKey = "Antavo API Secret"
Date = "Request Date"
Environment = "Antavo API Environment"
  
kSecret = "ANTAVO" + SecretKey  
kDate = HMAC(kSecret, Date)  
kEnvironment = HMAC(kDate, Environment)  
kService = HMAC(kEnvironment, "api")  
SigningKey = HMAC(kService, "antavo_request")

Where:

  • The value of SecretKey (a string) is the Antavo API Secret.
  • The value of Date (a string) is the date of the request in the YYYYMMDD short date format. Note that it does not include the time.
  • The value of Environment (a string) is the Antavo API Environment which can be found on the API settings page of the Management UI.
  • Note that for the value of kSecret, no space is added when concatenating ANTAVO and the value of SecretKey.

HMAC(key, "data") represents a cryptographic HMAC-SHA256 function that returns a binary format hash (digest). The examples in the pseudocode have the secret cryptographic key as the first parameter followed by the data (message). The Signing Key generated in this way ensures the authenticity of the message that is signed with it. It is critical to ensure your implementation aligns with this key/data structure.

Generate the Request Signature

The Request Signature is produced by applying the derived Signing Key and the String to Sign as inputs to the keyed hash function (HMAC). After calculating the digest, the signature is converted from the binary value to a lowercase hexadecimal representation:

HMAC("String to Sign", "Signing Key")

E.g., 003ad782eee82faf95337727da3c62ab8f1e473bda2116dd767eef9b2c3b110f

4. Add the Request Signature to the HTTP request

The API signing information (including the Request Signature) must be added to the request in the HTTP Authorization header. Note that the content of the Authorization header is created after the Request Signature is calculated, so the Authorization header is not included in the list of signed headers.

📘

Note

The Antavo API Secret used to derive the Signing Key is never transmitted in the API signing information included in the request.

The following pseudocode demonstrates the construction of the Authorization header:

HashingAlgorithm Credential=EXAMPLE_API_KEY/CredentialScope, SignedHeaders=SignedHeaders, Signature=RequestSignature

To construct the value of the Authorization header, add:

E.g.:

ANTAVO-HMAC-SHA256 Credential=EXAMPLE_API_KEY/20241121/staging/api/antavo_request, SignedHeaders=content-type;date;host, Signature=d27ad9cd51a5045c1bf80bfd9cb09003d0f7dc38f2fd9b59688c440252e68d82
🚧

Note

  • There is no comma between the hashing algorithm and Credential.
  • Credential, SignedHeaders and Signature (together with their values) are separated by a comma.
  • There are no spaces between the Antavo API Key, the forward slash (/), and the credential scope.

Examples

The aim of the request examples in this section is to demonstrate the API signing process and to help ensure your implementation is Antavo-compatible.

The data provided in the examples cannot be used to query data from thestaging environment as it does not contain real values for the Antavo API Key, Antavo API Secret, and the customer ID.

📘

Note

If you are testing the API signing process with an HTTP request that includes real data, please note that there is a 5-minute acceptance window for each request. The timestamp of the request must be within that time-frame.

GET request

The following GET request queries detailed customer information including a summary of the customer's point expiration statistics for a customer whose ID is example-customer-ID.

GET https://api.staging.antavo.com/customers/example-customer-id?fields=expiring_points HTTP/1.1
Host: api.staging.antavo.com
Date: 20241121T142143Z

Step 1

To create the Canonical Request, concatenate the following components into a single string:

  • The HTTP request method, followed by an LF character: GET
  • The Canonical URI which includes the customer ID path parameter, followed by an LF character: /customers/example-customer-id
  • The sorted Canonical Query String, followed by an LF character: fields=expiring_points
  • The sorted Canonical Headers list, followed by an LF character. Note that the date is given in the YYYYMMDDTHHMMSSZ long date format:
date:20241121T142143Z
host:api.staging.antavo.com
📘

Note

Each canonical header (including the last one) is followed by an LF character, so adding an LF character after the Canonical Headers list results in two LF characters between the last canonical header and the Signed Headers list.

  • The sorted Signed Headers single-line list, followed by an LF character: date;host
  • The SHA-256 hashed payload (in this case, the payload is an empty string): e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
📘

Note

Each component of the Canonical Request is followed by a newline character (LF) except for the last one, the hashed payload.

GET
/customers/example-customer-id
fields=expiring_points
date:20241121T142143Z
host:api.staging.antavo.com

date;host
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Step 2

To create the String to Sign, concatenate:

  • The hashing algorithm, followed by an LF character: ANTAVO-HMAC-SHA256
  • The date and time of the request given in the YYYYMMDDTHHMMSSZ long date format, followed by an LF character: 20241121T142143Z
  • The Credential Scope string, followed by an LF character: 20241121/staging/api/antavo_request
  • The hashed Canonical Request string. Note that this component is not followed by an LF character: a76347a06493c9e40ed997972fa782a30408bc0d8cb943f4b5164166f53c3a19
ANTAVO-HMAC-SHA256
20241121T142143Z
20241121/staging/api/antavo_request
a76347a06493c9e40ed997972fa782a30408bc0d8cb943f4b5164166f53c3a19

Note that in subsequent code snippets, we'll refer to the String to Sign as STRING_TO_SIGN to keep the signing example short.

Step 3

This step uses the EXAMPLE_API_SECRET Antavo API Secret (as the SecretKey component) in order to derive a Signing Key for the request to the API in the staging environment on November 21, 2024.

  1. Generate the Signing Key:
HMAC(
  HMAC(
    HMAC(
      HMAC(
        "ANTAVOEXAMPLE_API_SECRET",
        "20241121",
      ),
      "staging",
    ),
    "api",
  ),
  "antavo_request",
)

Note that in subsequent code snippets, we'll refer to the Signing Key as SIGN_KEY to keep the signing example short.

  1. Generate the Request Signature by signing the String to Sign with the Signing Key:
HMAC(STRING_TO_SIGN, SIGN_KEY)
003ad782eee82faf95337727da3c62ab8f1e473bda2116dd767eef9b2c3b110f

Step 4

Construct the Authorization header using the computed signature:

ANTAVO-HMAC-SHA256 Credential=EXAMPLE_API_KEY/20241121/staging/api/antavo_request, SignedHeaders=date;host, Signature=003ad782eee82faf95337727da3c62ab8f1e473bda2116dd767eef9b2c3b110f

The resulting signed request looks like this:

GET https://api.staging.antavo.com/customers/example-customer-id?fields=expiring_points HTTP/1.1
Date: 20241121T142143Z
Host: api.staging.antavo.com
Authorization: ANTAVO-HMAC-SHA256 Credential=EXAMPLE_API_KEY/20241121/staging/api/antavo_request, SignedHeaders=date;host, Signature=003ad782eee82faf95337727da3c62ab8f1e473bda2116dd767eef9b2c3b110f

POST request

The following POST request creates a point_add event for a customer whose ID is example-customer-ID.

POST https://api.staging.antavo.com/events HTTP/1.1
Content-Type: application/json
Host: api.staging.antavo.com
Date: 20241121T142143Z

{"action":"point_add","customer":"example-customer-id","data":{"points":500}}

Step 1

To create the Canonical Request, concatenate the following components into a single string:

  • The HTTP request method, followed by an LF character: POST
  • The Canonical URI which includes the customer ID path parameter, followed by an LF character: /events
  • As there are no query parameters, anLF character is inserted to account for the empty Canonical Query String.
  • The sorted Canonical Headers list, followed by an LF character. Note that the date is given in the YYYYMMDDTHHMMSSZ long date format:
content-type:application/json
date:20241121T142143Z
host:api.staging.antavo.com
📘

Note

Each canonical header (including the last one) is followed by an LF character, so adding an LF character after the Canonical Headers list results in two LF characters between the last canonical header and the Signed Headers list.

  • The sorted Signed Headers single-line list, followed by an LF character: content-type;date;host
  • The SHA-256 hashed payload (in this case, the payload is an empty string): 3b1129debac2fe5ff332285caa93af125fa1bc7a857583172f8faddd0a1a7f68
📘

Note

Each component of the Canonical Request is followed by a newline character (LF) except for the last one, the hashed payload.

POST
/events

content-type:application/json
date:20241121T142143Z
host:api.staging.antavo.com

content-type;date;host
3b1129debac2fe5ff332285caa93af125fa1bc7a857583172f8faddd0a1a7f68

Step 2

To create the String to Sign, concatenate:

  • The hashing algorithm, followed by an LF character: ANTAVO-HMAC-SHA256
  • The date and time of the request given in the YYYYMMDDTHHMMSSZ long date format, followed by an LF character: 20241121T142143Z
  • The Credential Scope string, followed by an LF character: 20241121/staging/api/antavo_request
  • The hashed Canonical Request string. Note that this component is not followed by an LF character: a76347a06493c9e40ed997972fa782a30408bc0d8cb943f4b5164166f53c3a19
ANTAVO-HMAC-SHA256
20241121T142143Z
20241121/staging/api/antavo_request
6929caabdd4277f295dcb690c79a1aac49c9b8609802103e1d41e35f9764a16c

Note that in subsequent code snippets, we'll refer to the String to Sign as STRING_TO_SIGN to keep the signing example short.

Step 3

This step uses the EXAMPLE_API_SECRET Antavo API Secret (as the SecretKey component) in order to derive a Signing Key for the request to the API in the staging environment on November 21, 2024.

  1. Generate the Signing Key:
HMAC(
  HMAC(
    HMAC(
      HMAC(
        "ANTAVOEXAMPLE_API_SECRET",
        "20241121",
      ),
      "staging",
    ),
    "api",
  ),
  "antavo_request",
)

Note that in subsequent code snippets, we'll refer to the Signing Key as SIGN_KEY to keep the signing example short.

  1. Generate the Request Signature by signing the String to Sign with the Signing Key:
HMAC(STRING_TO_SIGN, SIGN_KEY)
d27ad9cd51a5045c1bf80bfd9cb09003d0f7dc38f2fd9b59688c440252e68d82

Step 4

Construct the Authorization header using the computed signature:

ANTAVO-HMAC-SHA256 Credential=EXAMPLE_API_KEY/20241121/staging/api/antavo_request, SignedHeaders=content-type;date;host, Signature=d27ad9cd51a5045c1bf80bfd9cb09003d0f7dc38f2fd9b59688c440252e68d82

The resulting signed request looks like this:

POST https://api.staging.antavo.com/events HTTP/1.1
Content-type: application/json
Date: 20241121T142143Z
Host: api.staging.antavo.com
Authorization: ANTAVO-HMAC-SHA256 Credential=EXAMPLE_API_KEY/20241121/staging/api/antavo_request, SignedHeaders=content-type;date;host, Signature=d27ad9cd51a5045c1bf80bfd9cb09003d0f7dc38f2fd9b59688c440252e68d82

{"action":"point_add","customer":"example-customer-id","data":{"points":500}}

Signing requests using PHP

The Antavo EscherPHP library can be used to calculate, sign, and verify API request signatures with the following configuration (where {environment} must be replaced with the respective Antavo API environment):

\Escher::create('{environment}/api/antavo_request')
   ->setAlgoPrefix('ANTAVO')
   ->setVendorKey('Antavo')
   ->setAuthHeaderKey('Authorization')
   ->setDateHeaderKey('Date')

The following examples utilize the Antavo EscherPHP library to sign requests made with Guzzle .

Signing a GET request

📘

Note

The following example code snippet can only be used to validate the API signing process described in the GET request example. It cannot be used to query customer data from the staging environment as it does not contain real values for the API key, API secret and customer ID.

$method = 'GET';
$url = 'https://api.staging.antavo.com/customers/example-customer-id?fields=expiring_points';

$escher = \Escher::create(
    'staging/api/antavo_request',
    new DateTime('2024-11-21 14:21:43')
)
    ->setVendorKey('Antavo')
    ->setAlgoPrefix('ANTAVO')
    ->setDateHeaderKey('Date')
    ->setAuthHeaderKey('Authorization');

$signedHeader = $escher->signRequest(
    'EXAMPLE_API_KEY',
    'EXAMPLE_API_SECRET',
    $method,
    $url,
    ''
);

$client = new \GuzzleHttp\Client();
$response = $client->request(
    $method,
    $url,
    [
        'headers' => $signedHeader
    ]
);

Signing a POST request

📘

Note

The following example code snippet can only be used to validate the API signing process described in the POST request example. It cannot be used to post an event in the staging environment as it does not contain real values for the API key, API secret and customer ID.

$method = 'POST';
$url = 'https://api.staging.antavo.com/events';
$customHeaders = ['Content-Type' => 'application/json'];
$body = json_encode([
    'action' => 'point_add',
    'customer' => 'example-customer-id',
    'data' => [
        'points' => 500,
    ]
]);

$escher = \Escher::create(
    'staging/api/antavo_request',
    new DateTime('2024-11-21 14:21:43')
)
    ->setVendorKey('Antavo')
    ->setAlgoPrefix('ANTAVO')
    ->setDateHeaderKey('Date')
    ->setAuthHeaderKey('Authorization');

$signedHeader = $escher->signRequest(
    'EXAMPLE_API_KEY',
    'EXAMPLE_API_SECRET',
    $method,
    $url,
    $body,
    $customHeaders,
    ['content-type','date','host']
);

$client = new \GuzzleHttp\Client();
$response = $client->request(
    $method,
    $url,
    [
        'body' => $body,
        'headers' => $signedHeader
    ]
);