# Webhook Signature Validation Guide

## Overview

This guide explains how to validate webhook signatures sent by Synqly. When Synqly sends webhooks to your endpoints, it signs the payload using HMAC-SHA256 to ensure authenticity and integrity.

## How Signatures Are Generated

Synqly uses the following process to sign webhook payloads:

1. **Algorithm**: HMAC-SHA256
2. **Encoding**: Hexadecimal
3. **Format**: `sha256=<hex_signature>`
4. **Header**: `Synqly-Signature`
5. **Secret**: The `Integrator Key` configured in your webhook settings


## Signature Validation Process

### Step 1: Extract the Signature

The signature is sent in the `Synqly-Signature` HTTP header and follows this format:


```
Synqly-Signature: sha256=a1b2c3d4e5f6...
```

### Step 2: Prepare the Payload

Use the raw request body exactly as received. Do not modify, parse, or reformat the JSON payload.

### Step 3: Generate Expected Signature

Using your `Integrator Key` (webhook secret), generate the expected signature:

#### Python Example


```python
import hashlib
import hmac

def validate_webhook_signature(payload_body, signature_header, integrator_key):
    """
    Validate a webhook signature from Synqly
    
    Args:
        payload_body (bytes): Raw request body
        signature_header (str): Value from Synqly-Signature header
        integrator_key (str): Your webhook secret (Integrator Key)
    
    Returns:
        bool: True if signature is valid, False otherwise
    """
    # Remove 'sha256=' prefix if present
    received_signature = signature_header.replace('sha256=', '')
    
    # Generate expected signature
    expected_signature = hmac.new(
        integrator_key.encode('utf-8'),
        payload_body,
        hashlib.sha256
    ).hexdigest()
    
    # Use timing-safe comparison
    return hmac.compare_digest(received_signature, expected_signature)

# Usage example
def handle_webhook(request):
    payload_body = request.body  # Raw bytes
    signature_header = request.headers.get('Synqly-Signature')
    integrator_key = 'your-integrator-key-here'
    
    if not validate_webhook_signature(payload_body, signature_header, integrator_key):
        return {'error': 'Invalid signature'}, 401
    
    # Process webhook payload
    return {'status': 'success'}, 200
```

#### Go Example


```go
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "io"
    "net/http"
    "strings"
)

// ValidateWebhookSignature validates a webhook signature from Synqly
func ValidateWebhookSignature(payloadBody []byte, signatureHeader, integratorKey string) bool {
    // Remove 'sha256=' prefix if present
    receivedSignature := strings.TrimPrefix(signatureHeader, "sha256=")
    
    // Generate expected signature
    mac := hmac.New(sha256.New, []byte(integratorKey))
    mac.Write(payloadBody)
    expectedSignature := hex.EncodeToString(mac.Sum(nil))
    
    // Use timing-safe comparison
    return hmac.Equal([]byte(receivedSignature), []byte(expectedSignature))
}

// Usage example
func webhookHandler(w http.ResponseWriter, r *http.Request) {
    payloadBody, _ := io.ReadAll(r.Body)
    signatureHeader := r.Header.Get("Synqly-Signature")
    integratorKey := "your-integrator-key-here"
    
    if !ValidateWebhookSignature(payloadBody, signatureHeader, integratorKey) {
        http.Error(w, "Invalid signature", http.StatusUnauthorized)
        return
    }
    
    // Process webhook payload
    w.WriteHeader(http.StatusOK)
}
```

## Security Best Practices

### 1. Always Use Timing-Safe Comparison

Never use simple string comparison (`==` or `equals()`) to compare signatures. Use timing-safe comparison functions:

- Python: `hmac.compare_digest()`
- Go: `hmac.Equal()`


### 2. Validate Before Processing

Always validate the signature before processing the webhook payload.

### 3. Handle Missing Signatures


```python
def validate_webhook_signature(payload_body, signature_header, integrator_key):
    if not signature_header:
        raise ValueError("Missing signature header")
    
    if not signature_header.startswith('sha256='):
        raise ValueError("Invalid signature format")
    
    # Continue with validation...
```

### 4. Use Raw Request Body

Always use the raw, unmodified request body for signature validation. Do not:

- Parse JSON first
- Modify whitespace
- Change encoding
- Reformat the payload


### 5. Store Secrets Securely

- Store your integrator key securely and inject at runtime
- Never commit secrets to version control
- Rotate keys periodically


## Integration Examples

### Flask Example


```python
from flask import Flask, request, jsonify
import os

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def handle_webhook():
    payload_body = request.get_data()  # Raw bytes
    signature_header = request.headers.get('Synqly-Signature')
    integrator_key = os.environ.get('INTEGRATOR_KEY')
    
    if not validate_webhook_signature(payload_body, signature_header, integrator_key):
        return jsonify({'error': 'Invalid signature'}), 401
    
    # Parse JSON after validation
    payload = request.get_json()
    
    # Process webhook
    print(f'Webhook received: {payload}')
    return jsonify({'status': 'success'})
```

### Go HTTP Server Example


```go
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
)

func main() {
    http.HandleFunc("/webhook", webhookHandler)
    log.Println("Starting webhook server on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }
    
    payloadBody, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Failed to read body", http.StatusBadRequest)
        return
    }
    
    signatureHeader := r.Header.Get("Synqly-Signature")
    integrationKey := os.Getenv("INTEGRATOR_KEY")
    
    if !ValidateWebhookSignature(payloadBody, signatureHeader, integrationKey) {
        http.Error(w, "Invalid signature", http.StatusUnauthorized)
        return
    }
    
    // Parse JSON after validation
    var payload map[string]interface{}
    if err := json.Unmarshal(payloadBody, &payload); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    
    // Process webhook
    fmt.Printf("Webhook received: %+v\n", payload)
    
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(map[string]string{"status": "success"})
}
```

## Testing Your Implementation

You can test your signature validation using the following test vector:


```
Payload: {"test": "data"}
Secret: "test-secret"
Expected Signature: sha256=4b04c13cf8b8fa3b993c8a7e6c9dc6e0eddb0b2cee7b468cf3ed6b4b6fdda1a5
```

## Troubleshooting

### Common Issues

1. **Signature doesn't match**
  - Ensure you're using the raw request body
  - Verify the correct `integrator_key`
  - Check that you're not modifying the payload
2. **Missing signature header**
  - Check if webhooks are properly configured
  - Verify the `integrator_key` is set in webhook configuration
3. **Encoding issues**
  - Always use UTF-8 encoding for the secret
  - Use the raw bytes of the request body
4. **If you encounter issues with webhook signature validation**
  - Verify your `integrator_key` is correctly configured
  - Test with the provided test vectors
  - Check your implementation against the examples above
  - Ensure you're using timing-safe comparison functions


### Debug Helper


```python
def debug_signature_validation(payload_body, signature_header, integrator_key):
    print(f"Payload length: {len(payload_body)}")
    print(f"Payload (first 100 chars): {payload_body[:100]}")
    print(f"Received signature: {signature_header}")
    print(f"Secret length: {len(integrator_key)}")
    
    # Generate expected signature
    expected_signature = hmac.new(
        integrator_key.encode('utf-8'),
        payload_body,
        hashlib.sha256
    ).hexdigest()
    
    print(f"Expected signature: sha256={expected_signature}")
    
    return validate_webhook_signature(payload_body, signature_header, integrator_key)
```

## Webhook Headers Reference

| Header | Description | Example |
|  --- | --- | --- |
| `Synqly-Signature` | HMAC-SHA256 signature in hex format | `sha256=a1b2c3d4...` |
| `Content-Type` | Always `application/json; charset=utf-8` | `application/json; charset=utf-8` |