---
updatedAt: 2026-01-06T17:45:48.000Z
agentTools:
  projectIndex: https://developers.checkatrade.com/llms.txt
---

# HMAC Authentication

A detailed guide and code examples for how to generate the correct signature.

## Step-by-Step Signature Generation

### Step 1: Get the current timestamp

```javascript
const date = new Date().toISOString();
// Example: "2026-01-06T14:30:00.000Z"nce: api-reference.md
```

### Step 2: Create the signing string

```javascript
const signingString = `date: ${date}`;
// Example: "date: 2026-01-06T14:30:00.000Z"
```

### Step 3: Generate the HMAC-SHA256 signature

```javascript
import { createHmac } from 'crypto';

const signature = createHmac('sha256', YOUR_SECRET)
  .update(signingString)
  .digest('base64');
```

### Step 4: Build the Authorisation header

```javascript
const authorization = `Signature keyId="${YOUR_KEY}",algorithm="hmac-sha256",signature="${signature}"`;
```

### Step 5: Include both headers in your request

```javascript
headers: {
  'Authorization': authorization,
  'Date': date,
  'Content-Type': 'application/json'
}
```

## HTTP signature scheme

The signature is based on this
draft ["Signing HTTP Messages"](https://tools.ietf.org/html/draft-cavage-http-signatures-09). Your application must
provide to the client application both unique identifier:

* **key**: A key used to identify the client application;
* **shared secret**: A secret key shared between your application and the client application used to sign the requests
  and authenticate the client application.

### HTTP header

The signature must be sent in the HTTP header "Authorization" with the authentication scheme "Signature":

```
Authorization: Signature keyId="API_KEY",algorithm="hmac-sha256",headers="(request-target) host date digest content-length",signature="Base64(HMAC-SHA256(signing string))"  
```

The different components of the signature are:

* **keyId (REQUIRED)**: The client application's key.
* **algorithm (REQUIRED)**: The algorithm used to create the signature.
* **header (OPTIONAL)**: The list of HTTP headers used to create the signature of the request. If specified, it should
  be a lowercased, quoted list of HTTP header fields, separated by a single space character. If not specified,
  the `Date` header is used by default therefore the client must send this `Date` header. Note : The list order is
  important, and must be specified in the order the HTTP header field-value pairs are concatenated together during
  signing.
* **signature (REQUIRED)**: A base 64 encoded digital signature. The client uses the `algorithm` and `headers`
  signature parameters to form a canonicalized `signing string`.

### Signature string construction

To generate the string that is signed with the shared secret and the `algorithm`, the client must use the values of each
HTTP header field in the `headers` Signature parameter in the order they appear.

To include the HTTP request target in the signature calculation, use the special `(request-target)` header field name.

1. If the header field name is `(request-target)` then generate the header field value by concatenating the lowercased
   HTTP method, an ASCII space, and the path pseudo-headers (example : get /protected);
2. Create the header field string by concatenating the lowercased header field name followed with an ASCII colon `:`, an
   ASCII space ` ` and the header field value. If there are multiple instances of the same header field, all header field
   values associated with the header field must be concatenated, separated by a ASCII comma and an ASCII space `,`, and
   used in the order in which they will appear in the HTTP request;
3. If value is not the last value then append an ASCII newline `\n`.

To illustrate the rules specified above, assume a `headers` parameter list with the value
of `(request-target) host date cache-control x-test` with the following HTTP request headers:

```
GET /protected HTTP/1.1  
Host: example.org  
Date: Tue, 10 Apr 2018 10:30:32 GMT  
x-test: Hello world  
Cache-Control: max-age=60  
Cache-Control: must-revalidate  
```

For the HTTP request headers above, the corresponding signature string is:

```
(request-target): get /protected  
host: example.org  
date: Tue, 10 Apr 2018 10:30:32 GMT  
cache-control: max-age=60, must-revalidate  
x-test: Hello world  
```

> **Note**: For the purposes of signature construction, the URL as seen by the service is "/jobs", so when providing the (request-target), it should be:
> `(request-target): post /jobs`.

### Signature creation

In order to create a signature, a client must:

1. Create the signature string as described in [signature string construction](#signature-string-construction);

2. The `algorithm` and shared secret associated with `keyId` must then be used to generate a digital signature on the
   signature string;

3. The `signature` is then generated by base 64 encoding the output of the digital signature algorithm.

### Supported algorithms

Currently supported algorithm names are:

* hmac-sha1
* hmac-sha256
* hmac-sha512

## Complete Code Examples

### JavaScript/Node.js

```javascript
import { createHmac } from 'crypto';

const API_KEY = 'your-key';
const API_SECRET = 'your-secret';
const BASE_URL = 'https://api.staging.checkatrade.com/v1/affiliate-job';

async function createJob(jobData) {
  const date = new Date().toISOString();
  const signature = createHmac('sha256', API_SECRET)
    .update(`date: ${date}`)
    .digest('base64');
  
  const response = await fetch(`${BASE_URL}/jobs`, {
    method: 'POST',
    headers: {
      'Authorization': `Signature keyId="${API_KEY}",algorithm="hmac-sha256",signature="${signature}"`,
      'Date': date,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(jobData)
  });
  
  return response.json();
}

// Usage
const result = await createJob({
  categoryId: 20,
  description: "My boiler needs servicing and is making a strange noise",
  email: "customer@example.com",
  phone: "+447123456789",
  firstName: "Jane",
  lastName: "Doe",
  postcode: "SW1A 1AA"
});
```

### Python

```python
import hmac
import hashlib
import base64
import requests
from datetime import datetime, timezone

API_KEY = 'your-key'
API_SECRET = 'your-secret'
BASE_URL = 'https://api.staging.checkatrade.com/v1/affiliate-job'

def create_signature(secret: str, date: str) -> str:
    signing_string = f"date: {date}"
    signature = hmac.new(
        secret.encode('utf-8'),
        signing_string.encode('utf-8'),
        hashlib.sha256
    ).digest()
    return base64.b64encode(signature).decode('utf-8')

def create_job(job_data: dict) -> dict:
    date = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
    signature = create_signature(API_SECRET, date)
    
    headers = {
        'Authorization': f'Signature keyId="{API_KEY}",algorithm="hmac-sha256",signature="{signature}"',
        'Date': date,
        'Content-Type': 'application/json'
    }
    
    response = requests.post(f'{BASE_URL}/jobs', json=job_data, headers=headers)
    return response.json()

# Usage
result = create_job({
    'categoryId': 20,
    'description': 'My boiler needs servicing and is making a strange noise',
    'email': 'customer@example.com',
    'phone': '+447123456789',
    'firstName': 'Jane',
    'lastName': 'Doe',
    'postcode': 'SW1A 1AA'
})
```

### PHP

```php
<?php

$apiKey = 'your-key';
$apiSecret = 'your-secret';
$baseUrl = 'https://api.staging.checkatrade.com/v1/affiliate-job';

function createSignature($secret, $date) {
    $signingString = "date: $date";
    return base64_encode(hash_hmac('sha256', $signingString, $secret, true));
}

function createJob($jobData) {
    global $apiKey, $apiSecret, $baseUrl;
    
    $date = gmdate('Y-m-d\TH:i:s.v\Z');
    $signature = createSignature($apiSecret, $date);
    
    $ch = curl_init("$baseUrl/jobs");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => json_encode($jobData),
        CURLOPT_HTTPHEADER => [
            "Authorization: Signature keyId=\"$apiKey\",algorithm=\"hmac-sha256\",signature=\"$signature\"",
            "Date: $date",
            "Content-Type: application/json"
        ]
    ]);
    
    $response = curl_exec($ch);
    curl_close($ch);
    return json_decode($response, true);
}

// Usage
$result = createJob([
    'categoryId' => 20,
    'description' => 'My boiler needs servicing and is making a strange noise',
    'email' => 'customer@example.com',
    'phone' => '+447123456789',
    'firstName' => 'Jane',
    'lastName' => 'Doe',
    'postcode' => 'SW1A 1AA'
]);
```