How to Handle Genesys AudioHook Authentication and Signature Comparison

How to Handle Genesys AudioHook Authentication and Signature Comparison

Hey everyone! I’ve been working with the Genesys AudioHook API and getting the authentication right was a bit trickier than expected. I saw some other devs having troubles on this forum as well... so, here’s a couple of Python and JavaScript examples to save you some time.

This will show you how to build the canonical signature base, compute the HMAC-SHA256 signature, and make sure everything matches up.


Example Request

This is what the Genesys docs give us as an example GET request:

GET /api/v1/voicebiometrics/ws HTTP/1.1
Host: audiohook.example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: SSdtIGEgV2ViU29ja2V0IQ==
Sec-WebSocket-Version: 13
User-Agent: GenesysCloud-AudioHook-Client
Audiohook-Organization-Id: d7934305-0972-4844-938e-9060eef73d05
Audiohook-Correlation-Id: e160e428-53e2-487c-977d-96989bf5c99d
Audiohook-Session-Id: 30b0e395-84d3-4570-ac13-9a62d8f514c0
X-API-KEY: SGVsbG8sIEkgYW0gdGhlIEFQSSBrZXkh
Signature: sig1=:NZBwyBHRRyRoeLqy1IzOa9VYBuI8TgMFt2GRDkDuJh4=:
Signature-Input: sig1=(”@request-target” “@authority” “audiohook-organization-id”
“audiohook-session-id” “audiohook-correlation-id” “x-api-key”);
keyid=“SGVsbG8sIEkgYW0gdGhlIEFQSSBrZXkh”;
nonce=“VGhpc0lzQVVuaXF1ZU5vbmNl”;alg=“hmac-sha256”;created=1641013200;expires=3282026430


It assumes the following configuration: 
- Request URI: wss://audiohook.example.com/api/v1/voicebiometrics/ws
- API Key: SGVsbG8sIEkgYW0gdGhlIEFQSSBrZXkh
- Client Secret: TXlTdXBlclNlY3JldEtleVRlbGxOby0xITJAMyM0JDU=.

Python example

import base64
import hashlib
import hmac

# Secret Key (Base64 encoded)
CLIENT_SECRET = "TXlTdXBlclNlY3JldEtleVRlbGxOby0xITJAMyM0JDU="
secret = base64.b64decode(CLIENT_SECRET)

# Canonical Signature Base
signature_base = (
    '"@request-target": /api/v1/voicebiometrics/ws\n'
    '"@authority": audiohook.example.com\n'
    '"audiohook-organization-id": d7934305-0972-4844-938e-9060eef73d05\n'
    '"audiohook-session-id": 30b0e395-84d3-4570-ac13-9a62d8f514c0\n'
    '"audiohook-correlation-id": e160e428-53e2-487c-977d-96989bf5c99d\n'
    '"x-api-key": SGVsbG8sIEkgYW0gdGhlIEFQSSBrZXkh\n'
    '"@signature-params": ("@request-target" "@authority" "audiohook-organization-id" '
    '"audiohook-session-id" "audiohook-correlation-id" "x-api-key");'
    'keyid="SGVsbG8sIEkgYW0gdGhlIEFQSSBrZXkh";nonce="VGhpc0lzQVVuaXF1ZU5vbmNl";'
    'alg="hmac-sha256";created=1641013200;expires=3282026430'
)

# Compute the HMAC-SHA256 signature
computed_hash = base64.b64encode(
    hmac.new(secret, signature_base.encode("ascii"), hashlib.sha256).digest()
).decode("ascii")

# Expected hash from the HTTP header
expected_hash = "NZBwyBHRRyRoeLqy1IzOa9VYBuI8TgMFt2GRDkDuJh4="

# Output results
print("Computed Hash: ", computed_hash)
print("Expected Hash: ", expected_hash)

# Output
"Computed Hash:  NZBwyBHRRyRoeLqy1IzOa9VYBuI8TgMFt2GRDkDuJh4="
"Expected Hash:  NZBwyBHRRyRoeLqy1IzOa9VYBuI8TgMFt2GRDkDuJh4="

Javascript Example

const crypto = require('crypto');

// Secret Key (Base64 encoded)
const secret = Buffer.from('TXlTdXBlclNlY3JldEtleVRlbGxOby0xITJAMyM0JDU=', 'base64');

// Canonical Signature Base
const signature_base = [
    '"@request-target": /api/v1/voicebiometrics/ws',
    '"@authority": audiohook.example.com',
    '"audiohook-organization-id": d7934305-0972-4844-938e-9060eef73d05',
    '"audiohook-session-id": 30b0e395-84d3-4570-ac13-9a62d8f514c0',
    '"audiohook-correlation-id": e160e428-53e2-487c-977d-96989bf5c99d',
    '"x-api-key": SGVsbG8sIEkgYW0gdGhlIEFQSSBrZXkh',
    '"@signature-params": ("@request-target" "@authority" "audiohook-organization-id" ' +
    '"audiohook-session-id" "audiohook-correlation-id" "x-api-key");' +
    'keyid="SGVsbG8sIEkgYW0gdGhlIEFQSSBrZXkh";nonce="VGhpc0lzQVVuaXF1ZU5vbmNl";' +
    'alg="hmac-sha256";created=1641013200;expires=3282026430'
].join('\n');

// Compute the HMAC-SHA256 signature
const computed_hash = crypto.createHmac('sha256', secret).update(signature_base).digest('base64');

// Expected hash from the HTTP header
const expected_hash = "NZBwyBHRRyRoeLqy1IzOa9VYBuI8TgMFt2GRDkDuJh4=";

// Output results
console.log("Computed Hash: ", computed_hash);
console.log("Expected Hash: ", expected_hash);

//Output
"Computed Hash:  NZBwyBHRRyRoeLqy1IzOa9VYBuI8TgMFt2GRDkDuJh4="
"Expected Hash:  NZBwyBHRRyRoeLqy1IzOa9VYBuI8TgMFt2GRDkDuJh4="

Things to Watch For

  1. Order Matters: The order in @signature-params must match what the client sends.
  2. Line Breaks Are Important: Use \n for line breaks in the canonical signature base after the headers and include the keyid, nonce, alg... with no spaces, in the exact same order it comes (it may be shuffled request after request).
  3. Byte-Level Precision: The computed signature is very sensitive to even the smallest differences, like extra spaces.

Useful Links

Here are some resources that might help:


Wrapping Up

I hope this makes your life easier! If you’re running into issues, double-check the signature base construction order and make sure everything is built dynamically in the same order that it comes (can change every time), and that it matches byte-for-byte.

I hope this helped guys :blush:

Happy coding! :rocket:

Topic metadata

audiohook, authentication, HMAC, HMAC-SHA256, signature verification, canonical signature base, websocket protocol change, websocket authentication, API security, Python API, JavaScript API, API integration, signature-input, X-API-Key, dynamic signature validation, real-time authentication, Genesys API example walkthrough, authentication tutorial

1 Like