Using the Webhook Signature
Optionally (but recommended), during the webhook setup process in Teak's Dashboard, you can choose to provide a "secret," which will be used to generate a digital signature for the payload in order to ensure the authenticity of the POST request.
This signature will be passed as a header "X-Protecht-Signature"
The webhook signature is comprised of an HMAC-SHA256 hash using your secret and the JSON payload for the webhook. This signature is base64 encoded for transit in the header values and must be decoded before use.
Using this system you may verify the authenticity of a webhook via the following steps:
- Utilize HMAC-SHA256 to hash the JSON payload of the webhook with the secret chosen in the dashboard configuration stage
- Base64 decode the signature provided within the "X-Protecht-Signature" header.
- Compare the provided value with your calculated value.
Code Example:
import base64
import hashlib
import hmac
import json
import sys
from typing import Dict, Union
def verify_webhook_hash(payload: Dict, received_hash: str, webhook_secret: str) -> bool:
"""
Verify a received webhook hash against a payload.
Args:
payload (dict): The webhook payload to verify
received_hash (str): The base64-encoded hash received with the webhook
webhook_secret (str): Secret used in Teak webhook subscription
Returns:
bool: True if hash matches, False otherwise
Raises:
json.JSONDecodeError: If payload cannot be serialized to JSON
TypeError: If payload is not JSON-serializable
"""
try:
# Convert payload to JSON string
message = json.dumps(payload)
# Create HMAC hash
calculated_hash = hmac.new(
webhook_secret.encode("utf-8"),
message.encode("utf-8"),
digestmod=hashlib.sha256
).digest()
# Convert to base64 for comparison
calculated_hash_b64 = base64.b64encode(calculated_hash).decode('utf-8')
# Use constant-time comparison
return hmac.compare_digest(calculated_hash_b64, received_hash)
except (json.JSONDecodeError, TypeError) as e:
print(f"Error processing payload: {str(e)}")
return False
def main():
# Example payload and hash
received_payload = {
"data":{
"logo": None,
"client": {
"id": "68ca206b-9a6c-4c22-ba32-2aafe0e82324",
"logo": None,
"name": "Example Client",
"slug": "example-client",
"display_name": "Example Client",
"has_phone_support": False,
"policy_issued_email_text": None
},
"issued": "2025-01-01T00:00:00.000Z",
"status": "issued",
"created": "2025-01-01T00:00:00.000Z",
"premium": "3.99",
"product": "Optional RaaS",
"updated": "2025-01-01T00:00:00.000Z",
"coverage": "30.00",
"currency": "USD",
"customer": {
"id": "4ba6ef76-bb32-4648-a5a3-f0ea2b0f3409",
"email": "[email protected]",
"phone": "5555555555",
"last_name": "Doe",
"first_name": "John",
"billing_address": {
"city": "Phoenix",
"state": "AZ",
"country": "US",
"address1": "123 Main St",
"address2": "",
"zip_code": "85018"
}
},
"item_name": "Awesome Concert General Admission",
"client_name": "Example Client",
"order_number": "123456789",
"customer_name": "John Doe",
"policy_number":"dfac776a-210d-40a3-bafc-b0885127080b",
"affiliate_name":"Example Affiliate",
"reference_number":"tix-0001",
"v2_policy_number":"ORAAS-ABCDE12345",
"protecht_order_id":"c7f87f10-f594-4656-90e8-d006523d5ece",
"policy_issued_email_text":""
},
"action":"Issued",
"resource":"Policy"
}
# Value from X-Protecht-Signature header
received_hash = "dGhpcyBpcyBqdXN0IGFuIGV4YW1wbGUgaGFzaAo="
# Secret used in webhook subscription
webhook_secret = "your-webhook-secret"
# Verify the hash
is_valid = verify_webhook_hash(received_payload, received_hash, webhook_secret)
if is_valid:
print("✓ Webhook signature is valid")
sys.exit(0)
else:
print("⨯ Webhook signature is invalid")
sys.exit(1)
if __name__ == "__main__":
main()Additional Notes:
- The order of the payload is important, please use the payload as is.
- The Protecht-Signature is base64 encoded for transit. As such, please base64 encode the hash before comparing
Updated 8 months ago
