Signing Requests

AbeBooks APIs use HMAC-SHA256 request signing to verify that API requests are genuine and unaltered. This prevents tampering in transit and protects against replay attacks.

Request signing is used by the following services:

Prerequisites

You will need a valid access key and secret key pair. Where you generate these depends on the service:

Service Key Management
Order Update API Manage API Keys
Inventory Update API Manage API Keys
Purchase API (Private) Manage Purchase API Keys

See Managing Keys for details on creating multiple keys, authorizing them for specific services, and rotating credentials.

Signing Process

Task 1: Generate a checksum for the request content

Create a SHA-256 checksum of your message content. The message_content variable is the body of the message you plan to send, without headers. If the body is empty (common for GET requests), use an empty string '' instead.

content_checksum = HexEncode(SHA256(message_content))

Sample content checksum for the string "sample payload":

eee57820203860ea469843dfba7bbb970021cae59fcc6e99056937bdec33fd02

Task 2: Collect signature elements

The unsigned request consists of four elements that you concatenate together into a single string ready for signing.

First element — The request method (GET, PUT, POST, DELETE) in all upper-case:

POST

Second element — The URI of the request in canonical form. The canonical URI is the URI-encoded version of everything in the URI from the HTTP host, including any question mark character (?) and all query string parameters. Normalize URI paths according to RFC 3986 by removing redundant and relative path components and convert everything to lower-case (except where upper-case is required in any parameters):

Service Endpoint
Order Update API https://orderupdate.abebooks.com:10027/
Inventory Update API https://inventoryupdate.abebooks.com:10027/
Purchase API (Private) https://purchase.api.abebooks.com/v1/orders

Third element — A timestamp for the request, expressed in ISO 8601 format (YYYY-MM-DDThh:mm:ssZ) using UTC. Your request is valid for five minutes from this timestamp:

2017-09-18T23:25:35Z

Fourth element — The content checksum calculated in Task 1:

eee57820203860ea469843dfba7bbb970021cae59fcc6e99056937bdec33fd02

Task 3: Create the final string

Concatenate all elements together, placing a single newline character between each:

action = 'POST'
uri = 'https://orderupdate.abebooks.com:10027/'
abe_date = '2017-09-18T23:25:35Z'
content_checksum = 'eee57820203860ea469843dfba7bbb970021cae59fcc6e99056937bdec33fd02'
final_string = action + '\n' + uri + '\n' + abe_date + '\n' + content_checksum

Task 4: Generate the request signature

Use your secret key with a SHA-256 HMAC algorithm to sign the final string:

secret_key = '9ea20986-8f49-42f1-aa27-63EXAMPLEKEY'
access_key = 'EXAMPLEACCESSKEY'

abe_signature = HMAC(secret_key, final_string, SHA256).hexdigest()

Task 5: Submit the request

Call the API with the appropriate request method and URI, providing the following headers:

Header Description
Abe-Date The timestamp used during signing
Abe-Access-Key Your API access key
Abe-Signature The signature calculated above
Abe-RequestId A unique ID (GUID strongly recommended) for duplicate handling and error reporting

Example full HTTP request:

POST https://orderupdate.abebooks.com:10027/ HTTP/1.1
Abe-Date: 2017-09-18T23:25:35Z
Abe-Access-Key: EXAMPLEACCESSKEY
Abe-Signature: 9b6156bdd88cddf86fee8bab59533fdb23bf3887a890b62bba606d36396e6100
Abe-RequestId: f27d1de5-e37e-4760-b00c-d539cd7ce68e

<?xml version="1.0" encoding="ISO-8859-1"?>
<orderUpdateRequest>...</orderUpdateRequest>

Code Examples

Python

from datetime import datetime, timezone
import hashlib, hmac, uuid
import requests

# Task 1: get message body checksum (empty string if GET request)
message_body = "sample payload"
body_checksum = hashlib.sha256(message_body.encode()).hexdigest()

# Task 2: Gather signature elements
method = 'POST'
uri = 'https://orderupdate.abebooks.com:10027/'
timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')

# Task 3: create the final string
final_string = '\n'.join([method, uri, timestamp, body_checksum])

# Task 4: generate the request signature
access_key = 'SAMPLE_ACCESS_KEY'
secret_key = 'SAMPLE_SECRET_KEY'
signature = hmac.new(
    key=secret_key.encode(),
    msg=final_string.encode(),
    digestmod=hashlib.sha256
).hexdigest()

# Task 5: submit the request
headers = {
    'Abe-Date': timestamp,
    'Abe-Access-Key': access_key,
    'Abe-Signature': signature,
    'Abe-RequestId': str(uuid.uuid4()),
    'Content-Type': 'application/xml'
}
response = requests.post(uri, data=message_body, headers=headers)

Java

import org.apache.http.client.methods.*;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.HttpEntity;
import org.apache.http.util.EntityUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;

public class SigningExample {

  public static final String HMAC_SHA256 = "HmacSHA256";

  public static void main(String[] args)
      throws IOException, NoSuchAlgorithmException, InvalidKeyException {

    String accessKey = "SAMPLE_ACCESS_KEY";
    String secretKey = "SAMPLE_SECRET_KEY";

    // Task 1: get message body checksum (empty string if GET request)
    String messageBody = "<orderUpdateRequest>...</orderUpdateRequest>";
    String bodyChecksum = getChecksum(messageBody);

    // Task 2: Gather signature elements
    String method = "POST";
    String uri = "https://orderupdate.abebooks.com:10027/";
    String dateTime = Instant.now().toString();

    // Task 3: create the final string
    String finalString = method + "\n" + uri + "\n" + dateTime + "\n" + bodyChecksum;

    // Task 4: generate the request signature
    String signature = getHMAC(secretKey, finalString);

    // Task 5: submit the request
    CloseableHttpClient httpClient = HttpClients.createDefault();
    HttpPost request = new HttpPost(uri);

    request.setEntity(new StringEntity(messageBody, ContentType.APPLICATION_XML));
    request.setHeader("Abe-Date", dateTime);
    request.setHeader("Abe-Access-Key", accessKey);
    request.setHeader("Abe-Signature", signature);
    request.setHeader("Abe-RequestId", UUID.randomUUID().toString());

    try (var response = httpClient.execute(request)) {
      System.out.println(response.getStatusLine());
      HttpEntity entity = response.getEntity();
      System.out.println(EntityUtils.toString(entity));
      EntityUtils.consume(entity);
    }
  }

  public static String getChecksum(String message) throws NoSuchAlgorithmException {
    MessageDigest md = MessageDigest.getInstance("SHA-256");
    md.update(message.getBytes(StandardCharsets.UTF_8));
    return Hex.encodeHexString(md.digest());
  }

  public static String getHMAC(String secretKey, String message)
      throws NoSuchAlgorithmException, InvalidKeyException {
    Mac mac = Mac.getInstance(HMAC_SHA256);
    mac.init(new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), HMAC_SHA256));
    return Hex.encodeHexString(mac.doFinal(message.getBytes(StandardCharsets.UTF_8)));
  }
}

C#

using System;
using System.Net;
using System.IO;
using System.Text;
using System.Security.Cryptography;

public class SigningExample
{
  static public void Main()
  {
    // Task 1: get message body checksum (empty string if GET request)
    string messageBody = "<orderUpdateRequest>...</orderUpdateRequest>";
    string bodyChecksum = GetChecksum(messageBody);

    // Task 2: Gather signature elements
    string method = "POST";
    string uri = "https://orderupdate.abebooks.com:10027/";
    string timeStamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");

    // Task 3: create the final string
    string finalString = method + "\n" + uri + "\n" + timeStamp + "\n" + bodyChecksum;

    // Task 4: generate the request signature
    string secretKey = "SAMPLE_SECRET_KEY";
    string signature = GetHMAC(secretKey, finalString);

    // Task 5: submit the request
    string accessKey = "SAMPLE_ACCESS_KEY";
    WebRequest request = WebRequest.Create(uri);
    request.Method = method;

    request.Headers.Add("Abe-Date", timeStamp);
    request.Headers.Add("Abe-Access-Key", accessKey);
    request.Headers.Add("Abe-Signature", signature);
    request.Headers.Add("Abe-RequestId", Guid.NewGuid().ToString());

    byte[] bodyBytes = Encoding.UTF8.GetBytes(messageBody);
    request.ContentType = "application/xml";
    request.ContentLength = bodyBytes.Length;
    Stream stream = request.GetRequestStream();
    stream.Write(bodyBytes, 0, bodyBytes.Length);

    using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
    {
      StreamReader reader = new StreamReader(response.GetResponseStream());
      Console.WriteLine(reader.ReadToEnd());
    }
  }

  private static string GetChecksum(string message)
  {
    byte[] bytes = Encoding.UTF8.GetBytes(message);
    using (var sha256 = SHA256.Create())
    {
      byte[] hash = sha256.ComputeHash(bytes);
      return BitConverter.ToString(hash).Replace("-", "").ToLower();
    }
  }

  private static string GetHMAC(string secret, string message)
  {
    byte[] keyBytes = Encoding.UTF8.GetBytes(secret);
    byte[] messageBytes = Encoding.UTF8.GetBytes(message);
    using (var hmac = new HMACSHA256(keyBytes))
    {
      byte[] hash = hmac.ComputeHash(messageBytes);
      return BitConverter.ToString(hash).Replace("-", "").ToLower();
    }
  }
}

Troubleshooting

Error Cause Solution
Signature mismatch Incorrect string construction Verify each element matches exactly — check for trailing whitespace or incorrect newlines
Request expired Timestamp older than 5 minutes Ensure your system clock is accurate and using UTC
Invalid access key Key not found or revoked Verify the key is active in your API Key Management page
Unauthorized service Key not authorized for this API Check your key's authorized services — each key must be explicitly authorized for the service you're calling