GuidesReference Endpoints
Guides

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:

  1. The order of the payload is important, please use the payload as is.
  2. The Protecht-Signature is base64 encoded for transit. As such, please base64 encode the hash before comparing