Signing API requests
We use HMAC (hash-based message authentication code) to verify that a request is coming from an expected source (you) and that the request has not been tampered with in transit. This is achieved by including both a workspace and secret key in each message, the latter of which is only known to the server and client. Using these values, the client will generate a unique HMAC representing its request to the server. The client sends that HMAC to the server, along with a timestamp and all the arguments and values it was going to send anyway. The server gets the request and re-generates its own unique HMAC based on the submitted values using the same methods the client used. The server will then compare the two HMACs, if they are equal, then the server trusts the client, and processes the request otherwise will throw a 401 HTTP error.
Creating Signature
signature = Base64(HMAC-SHA-256(WORKSPACE_SECRET, StringToSign))
WORKSPACE_SECRET is unique secret key for Workspace which should never be shared over network and should only be used to sign API keys. StringToSign
parameter will be created by appending the strings below separated by a new-line (“\n”) character:
StringToSign =
HTTP-Verb + "\n" +
Content-MD5 + "\n" +
Content-Type + "\n" +
Date + "\n" +
RequestURI
Definitions
HTTP Verb: the HTTP method (GET, POST,PUT, etc.)
Content-MD5: an MD5 hash of the exact request body (JSON Format). In case it's a GET request, use empty string.
Content-Type: value of the content type header. For post/put request it will be be application/json
. This header should be in lowercase only.
Date: Value of Date header. It should be sent always. The value of the date header must be in one of the RFC 2616 formats (http://www.ietf.org/rfc/rfc2616.txt)
RequestURI: the URL with the query string parameters. This is not the full path, just the path and query string after domain. For example : /event/
Calculating HMAC
Example inputs:
Workspace Secret Key : the shared secret key here
String To Sign : the message to hash here
(calculate this as per above, this is just an example for HMAC calculation)
Reference outputs for example inputs above:
as hexit : 4643978965ffcec6e6d73b36a39ae43ceb15f7ef8131b8307862ebc560e7f988
as base64 : RkOXiWX/zsbm1zs2o5rkPOsV9++BMbgweGLrxWDn+Yg=
const key = 'the shared secret key here';
const message = 'the message to hash here';
const getUtf8Bytes = str =>
new Uint8Array(
[...unescape(encodeURIComponent(str))].map(c => c.charCodeAt(0))
);
const keyBytes = getUtf8Bytes(key);
const messageBytes = getUtf8Bytes(message);
const cryptoKey = await crypto.subtle.importKey(
'raw', keyBytes, { name: 'HMAC', hash: 'SHA-256' },
true, ['sign']
);
const sig = await crypto.subtle.sign('HMAC', cryptoKey, messageBytes);
// to lowercase hexits
[...new Uint8Array(sig)].map(b => b.toString(16).padStart(2, '0')).join('');
// to base64
btoa(String.fromCharCode(...new Uint8Array(sig)));
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
)
func main() {
secret := []byte("the shared secret key here")
message := []byte("the message to hash here")
hash := hmac.New(sha256.New, secret)
hash.Write(message)
// to lowercase hexits
hex.EncodeToString(hash.Sum(nil))
// to base64
base64.StdEncoding.EncodeToString(hash.Sum(nil))
}
import hashlib
import hmac
import base64
message = bytes('the message to hash here', 'utf-8')
secret = bytes('the shared secret key here', 'utf-8')
hash = hmac.new(secret, message, hashlib.sha256)
# to lowercase hexits
hash.hexdigest()
# to base64
base64.b64encode(hash.digest())
import hashlib
import hmac
import base64
message = bytes('the message to hash here').encode('utf-8')
secret = bytes('the shared secret key here').encode('utf-8')
hash = hmac.new(secret, message, hashlib.sha256)
# to lowercase hexits
hash.hexdigest()
# to base64
base64.b64encode(hash.digest())
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import javax.xml.bind.DatatypeConverter;
class Main {
public static void main(String[] args) {
try {
String key = "the shared secret key here";
String message = "the message to hash here";
Mac hasher = Mac.getInstance("HmacSHA256");
hasher.init(new SecretKeySpec(key.getBytes(), "HmacSHA256"));
byte[] hash = hasher.doFinal(message.getBytes());
// to lowercase hexits
DatatypeConverter.printHexBinary(hash);
// to base64
DatatypeConverter.printBase64Binary(hash);
}
catch (NoSuchAlgorithmException e) {}
catch (InvalidKeyException e) {}
}
}
extern crate hmac;
extern crate sha2;
extern crate base64;
extern crate hex;
use sha2::Sha256;
use hmac::{Hmac, Mac};
fn main() {
type HmacSha256 = Hmac<Sha256>;
let secret = "the shared secret key here";
let message = "the message to hash here";
let mut mac = HmacSha256::new_varkey(secret.as_bytes()).unwrap();
mac.input(message.as_bytes());
let hash_message = mac.result().code();
// to lowercase hexits
hex::encode(&hash_message);
// to base64
base64::encode(&hash_message);
}
using System;
using System.Security.Cryptography;
using System.Text;
class MainClass {
public static void Main (string[] args) {
string key = "the shared secret key here";
string message = "the message to hash here";
byte[] keyByte = new ASCIIEncoding().GetBytes(key);
byte[] messageBytes = new ASCIIEncoding().GetBytes(message);
byte[] hashmessage = new HMACSHA256(keyByte).ComputeHash(messageBytes);
// to lowercase hexits
String.Concat(Array.ConvertAll(hashmessage, x => x.ToString("x2")));
// to base64
Convert.ToBase64String(hashmessage);
}
}
key = 'the shared secret key here'
message = 'the message to hash here'
signature = :crypto.hmac(:sha256, key, message)
# to lowercase hexits
Base.encode16(signature, case: :lower)
# to base64
Base.encode64(signature)
require 'openssl'
require 'base64'
key = 'the shared secret key here'
message = 'the message to hash here'
# to lowercase hexits
OpenSSL::HMAC.hexdigest('sha256', key, message)
# to base64
Base64.encode64(OpenSSL::HMAC.digest('sha256', key, message))
var crypto = require('crypto');
var key = 'the shared secret key here';
var message = 'the message to hash here';
var hash = crypto.createHmac('sha256', key).update(message);
// to lowercase hexits
hash.digest('hex');
// to base64
hash.digest('base64');
<?php
$key = 'the shared secret key here';
$message = 'the message to hash here';
// to lowercase hexits
hash_hmac('sha256', $message, $key);
// to base64
base64_encode(hash_hmac('sha256', $message, $key, true));
Auth Header
Use your workspace key
and calculated HMAC to send Authorization
header in following structure:
Authorization: __WORKSPACE_KEY__:__GENERATED_HMAC__
Example
To sign following request that needs to be sent to https://hub.suprsend.com/event/ as POST
request with content type application/json
{
"distinct_id": "13793",
"env": "ENV_API_KEY"
"$add": {
"BannerClick"
}
}
MD5 will calculated for above string which comes out to be 6dd84af19da9cbc04a46de33cf50ea61
. String that needs to be signed will be:
POST
6dd84af19da9cbc04a46de33cf50ea61
application/json
Thu, 04 Oct 2021 08:49:58 GMT
/event/
which should generate following HMAC signature with hash of SHA-256 with secret key: jdksjdks
e295edac8a67f6eea4ddd53567e70d9ddb38ee365dd6649b91ad83322664b1f3
with base64 encoding of
ZTI5NWVkYWM4YTY3ZjZlZWE0ZGRkNTM1NjdlNzBkOWRkYjM4ZWUzNjVkZDY2NDliOTFhZDgzMzIyNjY0YjFmMw==
This Signature should be added to Authorization
header like this:
Authorization: ENV_API_KEY:ZTI5NWVkYWM4YTY3ZjZlZWE0ZGRkNTM1NjdlNzBkOWRkYjM4ZWUzNjVkZDY2NDliOTFhZDgzMzIyNjY0YjFmMw==