GH GambleHub

Signature and verification of requests

The signature of the request proves the authenticity of the sender and the integrity of the content. Unlike TLS (which protects the channel), an applied signature makes each message verifiable and resistant to proxy, cache, and delayed delivery.

Objectives:

1. Authenticity (who sent) and integrity (did not change).

2. Uniqueness (protection against replays).

3. Decoupling from transport (works on top of HTTP, queues, webhooks).

4. Auditability (reproducible check after months).

1) Threat model (minimum)

Substitution of body/headers along the route.
Replay (repeat legitimate request).
Downgrade/strip caption.
Stealing integration secrets.
Non-synchronous clock (clock skew) and long queues.

2) Choice of primitive

HMAC (symmetry): simple and fast, the key is stored on both sides. Suitable for B2B webhooks and internal APIs.
RSA/ECDSA (asymmetry): private key from the sender, public key from the recipient. Suitable for open integrations and when it is important not to share a secret.
mTLS: transport layer mutual authentication often combined with NMAC/body signature.
JWT/JWS: convenient for bearer tokens and self-sufficient stamps; to sign the body, it is better to use canonicalization + JWS Detached/HTTP Message Signatures.
HTTP Message Signatures (signature of selected parts of the request): modern approach for REST.

Recommendation: for webhooks - HMAC + timestamp + nonce + body canonicalization; for public API - HTTP Message Signatures or JWS; at high risks - add mTLS.

3) Canonicalization (what exactly we sign)

You need to sign a deterministic string that is equally recoverable by both parties.

Reference composition:

method \n path_with_query_normalized \n content-type \n digest: SHA-256=BASE64(SHA256(body)) \n x-ts: <unix    iso> \n x-nonce: <uuid> \n host \n x-tenant: <tenant_id> \n
Total row:

canonical = join("\n", fields)
signature = HMAC(secret, canonical)  # или ECDSA_sign(private_key, canonical)
Rules:
  • Normalize the path and order of query parameters.
  • Spaces/unicode/case - fix (for example, lower-case headers, trim).
  • Large bodies - hash (Digest), not turn on "as is."

4) Title format

Example for HMAC:

X-Signature-Alg: hmac-sha256
X-Signature: v1=hex(hmac),ts=1730379005,nonce=550e8400-e29b-41d4-a716-446655440000,kid=prov_42
Digest: SHA-256=BASE64(SHA256(body))
X-Tenant: brand_eu
Example for asymmetry (ECDSA P-256):

Signature: keyId="prov_42", alg="ecdsa-p256-sha256",
ts="2025-10-31T12:30:05Z", nonce="550e...", headers="(request-target) host digest x-tenant",
sig="BASE64(raw_signature)"

Where 'kid '/' keyId' allows you to select a key from the registry (see rotation).

5) Verification at receiving end

Pseudocode:
python def verify(request):
1) Basic assert abs (now () - request. ts) <= ALLOWED_SKEW  # напр., 300 с assert not replayed(request. nonce, window = TTL) # store nonce/ts in KV

2) Restore canonical canonical = build_canonical (
method=request. method,
path=normalize_path(request. path, request. query),
content_type=request. headers["content-type"],
digest=hash_body(request. body),
ts=request. ts,
nonce=request. nonce,
host=request. headers["host"],
tenant=request. headers. get("x-tenant")
)

3) Get the key key = key_registry. get(request. kid) # secret (HMAC) или public key (ECDSA)

4) Verify if request signature. alg. startswith("hmac"):
ok = hmac_compare(key. secret, canonical, request. signature)
else:
ok = asym_verify(key. public, canonical, request. signature)

5) Solution if not ok: return 401, "SIGNATURE_INVALID"
return 200, "OK"

Constant-time HMAC comparison, storing 'nonce '/' (ts, event_id)' in fast KV (TTL ≥ delivery window).

6) Anti-replay and windows

Timestamp + Nonce: reject requests older than '± Δ' (e.g. 5 min) and nonce retries in this window.
For webhooks: use a stable 'event _ id' and an inbox table - this is more reliable than just nonce.
Re-delivery (retrays) should use the same ts/nonce/event_id, not generate new ones.

7) Multi-tenant and regions

Store keys per tenant/region: 'kid = <tenant>: <region>: <key _ id>'.
Separate secret pools and limits; observe data residency.
In the headings/canonicalization, indicate'X-Tenant 'and the region is part of the context being checked.

8) Key management and rotation

Key registry (KMS/Vault): 'kid', type, algorithm, status ('active', 'deprecating', 'retired'), 'valid _ from/valid _ to'.
Dual-secret: hold the current and next key simultaneously (the receiver accepts both).
Rotation on a schedule and on an event (compromise).
Key pinning (if possible) and restricting access to key materials.
Logs of access to keys and actions with them.

9) Combination with mTLS and OAuth

mTLS checks the channel and "who you are" at the certificate level.
The signature protects the message (useful through proxies/caches/queues).
OAuth/JWT complements authentication/authorization, but by itself does not guarantee the integrity of the body (unless signed in canonicalization).
Best practices: mTLS + body signature (Digest) + HMAC/ECDSA + short 'ts' -interval.

10) Errors and response codes

'401 SIGNATURE_INVALID' is an invalid signature/algorithm.
'401 KEY_REVOKED' - 'kid 'is invalid/expired.
'400 TIMESTAMP_OUT_OF_RANGE' - clock/window.
'409 NONCE_REPLAYED' - Redo detected.
'400 DIGEST_MISMATCH' - body changed.
'415 UNSUPPORTED_ALGORITHM' is an unresolved'alg '.
'429 TOO_MANY_ATTEMPTS' - key/tenant throttling.

Kick the exact cause in the machine-readable 'error _ code'; do not return secrets/canonicalization "as is."

11) Observability and audit

Metrics:
  • `verify_p95_ms`, `verify_error_rate`, `digest_mismatch_rate`, `replay_blocked_rate`, `alg_usage{hmac,ecdsa}`, `clock_skew_ms`.
  • Logs (structural): 'kid', 'alg', 'tenant', 'region', 'ts', 'nonce', 'digest _ hash', 'decision', 'reason'.
  • Tracing: attributes' signature. kid`, `signature. alg`, `signature. ts_skew`.
  • Audit: immutable log of rotation, key usage and tolerance flags.

12) Performance

Hash the body by streaming (don't keep it in your memory).
Cache public keys by 'kid' with short TTL and disability by event.
On edge/gateway, make preliminary checks (ts/nonce/format).
HMAC faster than ECDSA; ECDSA is more convenient for external integrations and "non-shared" keys.

13) Testing

Fixture sets: same requests → same canonicalization/signature; dirty spaces/query order/ → headers are stable.
Negative: invalid 'kid/alg', modified body/host, nonce repeat, obsolete ts, clock skew.
Property-based: Any equivalent queries produce a single canonical string.
Interop: cross-language checks (Go/Java/Node/Python).
Chaos: delays, retreats, on-the-fly key changes.

14) Playbooks (runbooks)

1. 'SIGNATURE _ INVALID'spike

Check key rotation, clock misalignment, changes in canonicalization at the sender.
Temporarily enable 'dual-accept' for old 'kid', notify partner.

2. 'REPLAYED 'growth

Increase the TTL of nonce storage, check the retrainers at the sender, check the clock skew.
Override abusive IP/ASN on edge.

3. 'DIGEST _ MISMATCH' massively

Check proxy/compression/rewriting headers; fix the canonicalization version.
Disable body/header intruders.

4. Key compromise

Immediately revoke 'kid', translate to 'next _ kid', regenerate all secrets/tokens, audit access.

15) Typical errors

Signing a "body part" or JSON without fixing the order → vulnerability to field permutation.
The absence of a'Digest' → proxy can change the body unnoticed.
A long 'ts' window without nonce is → open to replay.
Keep secrets in environment variables without KMS/Vault.
Compare signature not constant-time.
Ignore 'host '/' path' in canonicalization → forward attack.
Mix 'kid' different tenants and regions.

16) Pre-sale checklist

  • Canonicalization format defined (method, path + query, content-type, Digest, ts, nonce, host, tenant).
  • Implemented HMAC/ECDSA with 'kid', key registry and dual-secret.
  • Included anti-replay (nonce + ts) and inbox/event_id storage for webhooks.
  • Configured error codes/retray policy and throttling per tenant/key.
  • Observability: verify metrics, logs, trace, alerts for bursts.
  • Key rotation is automated; audit and access rights are limited.
  • Canonicalization and interlanguage compatibility test kits.
  • Documentation for integrators with an example in 3-4 languages ​ ​ and fixes.
  • mTLS enabled for sensitive integrations; JWT is used only as an addition, not a replacement for the signature of the body.

Conclusion

Signing and verifying requests is not "one header," but a discipline: clear canonicalization, short windows of time, anti-replay, key rotation, and observability. Build a single standard for all integrations (APIs and webhooks), use 'kid '/KMS, accept two keys during rotation, and your contours will become resistant to spoofing, predictable and easy to audit.

Contact

Get in Touch

Reach out with any questions or support needs.We are always ready to help!

Telegram
@Gamble_GC
Start Integration

Email is required. Telegram or WhatsApp — optional.

Your Name optional
Email optional
Subject optional
Message optional
Telegram optional
@
If you include Telegram — we will reply there as well, in addition to Email.
WhatsApp optional
Format: +country code and number (e.g., +380XXXXXXXXX).

By clicking this button, you agree to data processing.