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 |