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

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

Step 2: Create the signing string

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

Step 3: Generate the HMAC-SHA256 signature

import { createHmac } from 'crypto';

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

Step 4: Build the Authorisation header

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

Step 5: Include both headers in your request

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

HTTP signature scheme

The signature is based on this draft "Signing HTTP Messages". 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;

  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

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: "[email protected]",
  phone: "+447123456789",
  firstName: "Jane",
  lastName: "Doe",
  postcode: "SW1A 1AA"
});

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': '[email protected]',
    'phone': '+447123456789',
    'firstName': 'Jane',
    'lastName': 'Doe',
    'postcode': 'SW1A 1AA'
})

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' => '[email protected]',
    'phone' => '+447123456789',
    'firstName' => 'Jane',
    'lastName' => 'Doe',
    'postcode' => 'SW1A 1AA'
]);