Skip to main content

Signature Verification

Every webhook request includes cryptographic headers so you can verify that the request genuinely originates from SIPSIM and has not been tampered with.

Signature Headers

HeaderDescription
X-Webhook-SignatureHMAC-SHA256 hex digest of the signed payload
X-Webhook-TimestampUnix timestamp (seconds) used in signing

How It Works

The signature is computed as:

HMAC-SHA256(signing_secret, "{timestamp}.{raw_json_body}")

To verify a webhook:

  1. Extract the X-Webhook-Signature and X-Webhook-Timestamp headers
  2. Reconstruct the signed string: "{timestamp}.{raw_json_body}"
  3. Compute the HMAC-SHA256 using your signing secret
  4. Compare your computed signature with the header value
  5. Reject the request if they don't match

Code Examples

import hmac
import hashlib

def verify_webhook(request_body: bytes, signature: str, timestamp: str, secret: str) -> bool:
"""Verify a SIPSIM webhook signature."""
expected = hmac.new(
secret.encode(),
f"{timestamp}.{request_body.decode()}".encode(),
hashlib.sha256
).hexdigest()

return hmac.compare_digest(expected, signature)

# In your request handler:
# body = request.get_data()
# signature = request.headers.get("X-Webhook-Signature")
# timestamp = request.headers.get("X-Webhook-Timestamp")
# if not verify_webhook(body, signature, timestamp, "your_signing_secret"):
# return "Unauthorized", 401
Use Raw Body

Always use the raw request body (not a parsed-then-reserialized version) when computing the signature. Re-serializing JSON can change field order or whitespace, which will cause verification to fail.

Troubleshooting

ProblemSolution
Signature never matchesEnsure you're using the raw request body, not a parsed/reserialized version
Signature matched before but stoppedCheck if the signing secret was regenerated in the dashboard

Next Steps