1+ import hashlib
2+ import hmac
3+ from typing import Mapping
4+
5+ from apimatic_core_interfaces .security .signature_verifier import SignatureVerifier
6+
7+
8+ class HmacSignatureVerifier (SignatureVerifier ):
9+ """
10+ Verifier that checks webhook signatures using HMAC-SHA256.
11+ """
12+
13+ def __init__ (self , key : str , signature_header : str ):
14+ """
15+ Initialize the HMAC signature verifier.
16+
17+ Args:
18+ key (str): Secret key used for HMAC verification.
19+ signature_header (str): Name of the HTTP header that carries the signature.
20+
21+ Raises:
22+ ValueError: If key or signature_header is empty.
23+ """
24+ if not key :
25+ raise ValueError ("HMAC verification requires a non-empty key." )
26+ if not signature_header :
27+ raise ValueError ("signature_header must be a non-empty string." )
28+ self ._key = key
29+ self ._signature_header = signature_header .lower ()
30+
31+ def verify (self , headers : Mapping [str , str ], payload : str ) -> bool :
32+ """
33+ Verify the payload against the signature in headers.
34+
35+ Args:
36+ headers (Mapping[str, str]): HTTP headers of the request.
37+ payload (str): Raw request body as a JSON string.
38+
39+ Returns:
40+ bool: True if the computed HMAC matches the provided signature.
41+
42+ Raises:
43+ ValueError: If the signature header is missing.
44+ """
45+ normalized = {k .lower (): v for k , v in headers .items ()}
46+ provided = normalized .get (self ._signature_header )
47+ if not provided :
48+ raise ValueError (f"Missing required header: { self ._signature_header } " )
49+
50+ computed = hmac .new (
51+ self ._key .encode ("utf-8" ),
52+ payload .encode ("utf-8" ),
53+ hashlib .sha256
54+ ).hexdigest ()
55+
56+ return hmac .compare_digest (provided , computed )
0 commit comments