Skip to main content

Request Signing

Add an extra layer of security to your routes with cryptographic request signing. Verify that requests haven’t been tampered with and prevent replay attacks.

What is Request Signing?

Request signing uses HMAC-SHA256 (Hash-based Message Authentication Code) to create a cryptographic signature of each request. This signature proves:
  • Authenticity: Request came from a trusted source
  • Integrity: Request wasn’t modified in transit
  • Non-repudiation: Sender cannot deny sending the request

Why Use Request Signing?

Without Request Signing

Attacker intercepts request

Modifies payload amount: $10 → $1,000

Forwards modified request

Your backend processes fraudulent request ❌

With Request Signing

Attacker intercepts request

Modifies payload amount: $10 → $1,000

Signature verification fails ❌

Request rejected before reaching backend ✅

How It Works

Signature Creation (Client Side)

  1. Combine request components:
timestamp + method + path + body
  1. Create HMAC-SHA256 signature:
signature = HMAC-SHA256(secret_key, message)
  1. Include in request headers:
X-Signature: abc123def456...
X-Timestamp: 1640000000

Signature Verification (KnoxCall)

  1. Extract signature and timestamp from headers
  2. Recreate the message from request components
  3. Compute expected signature using shared secret
  4. Compare signatures:
    • Match → Request is valid ✅
    • Mismatch → Request is rejected ❌

Setting Up Request Signing

Step 1: Enable on Route

  1. Navigate to Routes → Select your route
  2. Scroll to Security section
  3. Toggle Require Signature to ON
  4. Configure settings:
Signature Header:
X-Signature
(Default, can be customized) Timestamp Header:
X-Timestamp
(Default, can be customized) Signature Algorithm:
HMAC-SHA256
(Most secure and widely supported) Timestamp Tolerance:
300 seconds (5 minutes)
Allows clock skew between client and server.
  1. Click Save

Step 2: Create Signing Secret

  1. Navigate to SecretsAdd Secret
  2. Configure:
Name:
my-signing-secret
Type:
Static Secret
Value:
[Generate a strong random key]
Recommended: Use a cryptographically secure random string:
openssl rand -hex 32
  1. Click Save

Step 3: Assign Secret to Client

  1. Navigate to Clients → Select your client
  2. Go to Signing Configuration tab
  3. Select the signing secret
  4. Click Save
Now this client must sign all requests to protected routes.

Client Implementation

JavaScript/Node.js

const crypto = require('crypto');

function signRequest(method, path, body, secret) {
  // 1. Get current timestamp
  const timestamp = Math.floor(Date.now() / 1000);

  // 2. Create message to sign
  const message = `${timestamp}.${method}.${path}.${JSON.stringify(body)}`;

  // 3. Create HMAC-SHA256 signature
  const signature = crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');

  return { signature, timestamp };
}

// Usage
const method = 'POST';
const path = '/api/orders';
const body = { orderId: '123', amount: 99.99 };
const secret = 'your-signing-secret';

const { signature, timestamp } = signRequest(method, path, body, secret);

// Make request with signature
fetch('https://your-subdomain.knoxcall.com/api/orders', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-knoxcall-route': 'my-route',
    'x-knoxcall-key': 'your-api-key',
    'x-signature': signature,
    'x-timestamp': timestamp.toString(),
  },
  body: JSON.stringify(body),
});

Python

import hmac
import hashlib
import time
import json
import requests

def sign_request(method, path, body, secret):
    # 1. Get current timestamp
    timestamp = int(time.time())

    # 2. Create message to sign
    message = f"{timestamp}.{method}.{path}.{json.dumps(body)}"

    # 3. Create HMAC-SHA256 signature
    signature = hmac.new(
        secret.encode(),
        message.encode(),
        hashlib.sha256
    ).hexdigest()

    return signature, timestamp

# Usage
method = 'POST'
path = '/api/orders'
body = {'orderId': '123', 'amount': 99.99}
secret = 'your-signing-secret'

signature, timestamp = sign_request(method, path, body, secret)

# Make request with signature
response = requests.post(
    'https://your-subdomain.knoxcall.com/api/orders',
    headers={
        'Content-Type': 'application/json',
        'x-knoxcall-route': 'my-route',
        'x-knoxcall-key': 'your-api-key',
        'x-signature': signature,
        'x-timestamp': str(timestamp),
    },
    json=body
)

Go

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "strconv"
    "time"
)

func signRequest(method, path string, body interface{}, secret string) (string, int64) {
    // 1. Get current timestamp
    timestamp := time.Now().Unix()

    // 2. Create message to sign
    bodyJSON, _ := json.Marshal(body)
    message := fmt.Sprintf("%d.%s.%s.%s", timestamp, method, path, bodyJSON)

    // 3. Create HMAC-SHA256 signature
    h := hmac.New(sha256.New, []byte(secret))
    h.Write([]byte(message))
    signature := hex.EncodeToString(h.Sum(nil))

    return signature, timestamp
}

// Usage
func main() {
    method := "POST"
    path := "/api/orders"
    body := map[string]interface{}{
        "orderId": "123",
        "amount":  99.99,
    }
    secret := "your-signing-secret"

    signature, timestamp := signRequest(method, path, body, secret)

    // Include in request headers:
    // x-signature: signature
    // x-timestamp: strconv.FormatInt(timestamp, 10)
}

PHP

<?php

function signRequest($method, $path, $body, $secret) {
    // 1. Get current timestamp
    $timestamp = time();

    // 2. Create message to sign
    $message = $timestamp . '.' . $method . '.' . $path . '.' . json_encode($body);

    // 3. Create HMAC-SHA256 signature
    $signature = hash_hmac('sha256', $message, $secret);

    return [$signature, $timestamp];
}

// Usage
$method = 'POST';
$path = '/api/orders';
$body = ['orderId' => '123', 'amount' => 99.99];
$secret = 'your-signing-secret';

list($signature, $timestamp) = signRequest($method, $path, $body, $secret);

// Make request with signature
$ch = curl_init('https://your-subdomain.knoxcall.com/api/orders');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'x-knoxcall-route: my-route',
    'x-knoxcall-key: your-api-key',
    'x-signature: ' . $signature,
    'x-timestamp: ' . $timestamp,
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$response = curl_exec($ch);
curl_close($ch);
?>

Signature Verification Process

KnoxCall performs the following checks:

1. Timestamp Validation

Current Time: 15:30:00
Request Timestamp: 15:28:30
Tolerance: 5 minutes

Time difference: 1.5 minutes ✅ VALID
Rejected if:
  • Timestamp is more than 5 minutes old
  • Timestamp is in the future (clock skew)

2. Signature Recreation

Extract from request:
- Method: POST
- Path: /api/orders
- Body: {"orderId":"123","amount":99.99}
- Timestamp: 1640000000

Recreate message:
1640000000.POST./api/orders.{"orderId":"123","amount":99.99}

Compute signature:
HMAC-SHA256(secret, message) = abc123...

3. Signature Comparison

Request Signature:  abc123def456...
Expected Signature: abc123def456...

Match? ✅ Request is authentic

Preventing Replay Attacks

Request signing combined with timestamps prevents replay attacks:
Attacker intercepts valid request

Timestamp: 15:00:00
Signature: abc123... (valid at 15:00:00)

Attacker replays request at 15:10:00

Timestamp check fails (> 5 minutes old)

Request rejected ❌

Nonce-Based Protection (Advanced)

For even stronger protection, add a nonce (number used once):
const crypto = require('crypto');

function signRequestWithNonce(method, path, body, secret) {
  const timestamp = Math.floor(Date.now() / 1000);
  const nonce = crypto.randomBytes(16).toString('hex');

  const message = `${timestamp}.${nonce}.${method}.${path}.${JSON.stringify(body)}`;

  const signature = crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');

  return { signature, timestamp, nonce };
}
KnoxCall tracks nonces to prevent the same request being sent twice, even within the timestamp window.

Error Responses

Invalid Signature

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "error": "Invalid signature",
  "message": "Request signature verification failed",
  "expected_format": "HMAC-SHA256 of 'timestamp.method.path.body'"
}

Timestamp Too Old

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "error": "Timestamp expired",
  "message": "Request timestamp is older than 5 minutes",
  "timestamp": 1640000000,
  "current_time": 1640000400,
  "max_age_seconds": 300
}

Missing Headers

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": "Missing signature headers",
  "message": "x-signature and x-timestamp headers are required",
  "required_headers": ["x-signature", "x-timestamp"]
}

Best Practices

1. Use Strong Secrets

Generate cryptographically secure signing secrets:
# Good: 64 hex characters (256 bits)
openssl rand -hex 32
Bad:
  • password123
  • my-secret-key
  • Short or predictable strings

2. Rotate Secrets Regularly

Rotate signing secrets every 90 days:
  1. Create new secret
  2. Update clients with new secret (gracefully)
  3. Support both old and new secrets for 24 hours
  4. Remove old secret

3. Use HTTPS Always

Request signing protects integrity, but not confidentiality:
HTTPS encrypts the entire request (including signature)
Signature prevents tampering after decryption
Always use HTTPS + request signing together.

4. Include All Relevant Data

Sign everything that matters:
// Good: Includes body and important headers
const message = `${timestamp}.${method}.${path}.${body}.${contentType}`;

// Bad: Only timestamp
const message = `${timestamp}`;

5. Handle Clock Skew

Allow 5-minute tolerance for client clock differences:
Server Time: 15:30:00
Client Time: 15:29:00 (1 minute behind)

Still accepted ✅

Use Cases

Payment Processing

Route: /api/payments
Require Signature: ON
Reason: Prevent payment amount tampering

Administrative Actions

Route: /api/admin/*
Require Signature: ON
Reason: Ensure admin requests are authentic

Webhook Validation

Route: /webhooks/external
Require Signature: ON
Reason: Verify webhooks came from real source

High-Value Transactions

Routes: /api/transfers, /api/withdrawals
Require Signature: ON
Reason: Prevent unauthorized financial transactions

Troubleshooting

”Invalid signature” errors

Check:
  • ✓ Using the correct signing secret
  • ✓ Message format matches exactly (order matters)
  • ✓ Body is JSON-stringified identically
  • ✓ No extra whitespace in headers
Debug: Log the message before signing:
console.log('Message to sign:', message);
console.log('Signature:', signature);

“Timestamp expired” errors

Causes:
  • Client clock is out of sync
  • Request took too long to reach KnoxCall
  • Network latency
Fix:
  • Sync client clock with NTP
  • Increase timestamp tolerance (temporarily for testing)
  • Reduce request queuing on client side

Signature works in test but not production

Check:
  • ✓ Using production signing secret (not test secret)
  • ✓ Correct route configuration in production
  • ✓ Client is assigned to production environment

Next Steps


📊 Statistics

  • Level: advanced
  • Time: 20 minutes

🏷️ Tags

security, hmac, signatures, authentication, integrity