Verify the exact bytes received
Read the provider's signature specification before choosing middleware. Signature schemes commonly authenticate the original request bytes, sometimes combined with a timestamp or other fields. Parsing and reserializing JSON can change those bytes even when the business values appear unchanged.
Stripe specifically requires the raw request body for signature verification. Prefer the provider's maintained verification library when available, and configure body handling accordingly. Require HTTPS and impose a request size limit before allocating unbounded memory to an unauthenticated request.
Treat the signature format as a protocol
Do not invent a generic signature parser and assume it works for every provider. Establish the signed fields, supported algorithm, encoding, key identification and comparison behavior. Reject malformed signatures rather than attempting permissive recovery. Use a timing safe comparison when implementing a protocol that requires comparing secret derived values.
Keep signing secrets in a managed secret store and scope them by endpoint and environment. Document rotation overlap and emergency revocation. Development fixtures must use separate secrets, and logs must never include the secret or a complete reusable authentication artifact.
This Python example checks an HMAC SHA256 signature over a decimal Unix timestamp, a literal period and the original request body. It limits body size, checks timestamp tolerance and compares the digest. Use the provider's verification library for its protocol, then deduplicate accepted events before processing.
import hashlib
import hmac
import re
def verify_reference_request(
raw_body: bytes, timestamp: str, signature: str,
secret: bytes, now: int, tolerance: int = 300,
) -> bool:
if not secret or tolerance < 0 or len(raw_body) > 1_048_576:
return False
if not re.fullmatch(r"[0-9]{1,11}", timestamp):
return False
if not re.fullmatch(r"[0-9a-f]{64}", signature):
return False
if abs(now - int(timestamp)) > tolerance:
return False
signed = timestamp.encode("ascii") + b"." + raw_body
expected = hmac.new(secret, signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Separate freshness from deduplication
A valid signature proves that covered data matches a holder of the signing material under the protocol's assumptions. It does not prove that the event has never been processed. Check timestamp tolerance where the provider includes a signed timestamp, and keep the server clock synchronized.
Then apply event deduplication at the durable processing boundary. A legitimate retry may carry a fresh signature for an existing event. Conversely, an attacker may replay a recently signed request within the allowed time window. Freshness and event identity therefore solve different parts of the problem.
Return failures and record verification results
Return an appropriate failure for invalid authentication and a success response only after the chosen durable acceptance step. Keep public error bodies brief. Internally record the endpoint identity, verification category and safe correlation fields so operators can distinguish configuration drift from malicious traffic.
Test altered bytes, missing headers, expired timestamps, rotated secrets, oversized requests and valid duplicate events. Include a durable storage failure after verification. A valid signature must never cause the endpoint to acknowledge work that it has already lost.