Skip to content
RateStack
API cookbook

Verify HMAC signatures across languages

The same verification pattern in TypeScript, Python, Go, and Ruby. Constant-time compare; 5-minute timestamp window.

RTBy RateStack TeamPublishedReviewed
beginnerTypeScript · Python · Go · Ruby

TypeScript

import crypto from "node:crypto";
function verify(body: string, sig: string, ts: number, secret: string): boolean {
  if (Math.abs(Date.now() / 1000 - ts) > 300) return false;
  const expected = crypto.createHmac("sha256", secret)
    .update(`${ts}.${body}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}

Python

import hmac, hashlib, time
def verify(body: str, sig: str, ts: int, secret: bytes) -> bool:
    if abs(time.time() - ts) > 300:
        return False
    expected = hmac.new(secret, f"{ts}.{body}".encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

Go

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "time"
)

func Verify(body, sig string, ts int64, secret []byte) bool {
    if d := time.Now().Unix() - ts; d > 300 || d < -300 {
        return false
    }
    mac := hmac.New(sha256.New, secret)
    mac.Write([]byte(fmt.Sprintf("%d.%s", ts, body)))
    expected := hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(sig))
}

Ruby

require "openssl"
def verify(body, sig, ts, secret)
  return false if (Time.now.to_i - ts).abs > 300
  expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{ts}.#{body}")
  Rack::Utils.secure_compare(expected, sig)
end
Verify HMAC signatures across languages — API cookbook | RateStack