> ## Documentation Index
> Fetch the complete documentation index at: https://docs.catenatelematics.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks Security & Validation

> Secure your webhook endpoints by verifying signatures and understanding request headers

Secure your webhook endpoints by verifying that each request genuinely came from Catena and hasn't been tampered with. Every webhook includes cryptographic signatures and comprehensive headers for authentication, tracking, and event routing.

<Warning>
  **Security Critical:** Always verify webhook signatures before processing events. Unverified webhooks expose your system to spoofing and tampering attacks.
</Warning>

***

## Request Headers

Every webhook request includes HTTP headers for security verification, event metadata, and delivery tracking. Here's what a typical webhook request looks like with all headers included:

```http theme={null}
POST /your-endpoint HTTP/1.1
Host: your-domain.com
Content-Type: application/json
Content-Encoding: gzip
User-Agent: Catena-Webhook
Accept: application/json
X-Catena-Signature: 2jSwOn+crDEbShY66aX1xn6uQXpAU9Ufw=
X-Catena-Timestamp: 2024-11-28T10:30:00+00:00
X-Event-Type: vehicle.modified
X-Webhook-ID: 247b2dea-a030-48b7-9a05-ee33c1b6ab0a
X-Request-ID: 62cb8fea-e017-4b08-86b7-4469fa872b91
X-Schema-Version: 1.2
X-Source: catena-notifications
X-Delivery-Attempt: 1
```

### Header Reference

| Header               | Example Value                          | Description                                                                                                                                                                                           |
| -------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-Catena-Signature` | `2jSwOn+crDEbShY66aX1xn6uQXpAU9Ufw=`   | Base64-encoded HMAC-SHA256 signature for verifying request authenticity. Computed as `HMAC_SHA256(webhook_secret, timestamp + "." + uncompressed_payload)`. Always validate before processing events. |
| `X-Catena-Timestamp` | `2024-11-28T10:30:00+00:00`            | RFC 3339 formatted UTC timestamp when the event was sent. Used for signature computation and replay attack prevention. Reject requests older than 5 minutes.                                          |
| `X-Event-Type`       | `vehicle.modified`                     | The type of event being delivered, matching the `event_name` from your webhook subscription. Use for routing events to appropriate handlers.                                                          |
| `X-Schema-Version`   | `1.2`                                  | The version of the event payload schema. Monitor for schema updates to handle changes gracefully.                                                                                                     |
| `X-Source`           | `catena-notifications`                 | Identifies the Catena service that generated the event. Useful for logging and debugging.                                                                                                             |
| `X-Request-ID`       | `62cb8fea-e017-4b08-86b7-4469fa872b91` | Unique identifier for this specific delivery attempt. Changes with each retry. Use for duplicate detection.                                                                                           |
| `X-Webhook-ID`       | `247b2dea-a030-48b7-9a05-ee33c1b6ab0a` | The unique identifier of your webhook subscription. Use when multiple webhooks share the same endpoint.                                                                                               |
| `X-Delivery-Attempt` | `1`, `2`, `3`                          | The delivery attempt number (starts at 1, increments with retries). Use for monitoring delivery reliability.                                                                                          |
| `Content-Type`       | `application/json`                     | Request body format (always JSON).                                                                                                                                                                    |
| `Content-Encoding`   | `gzip`                                 | Payload compression format. Event payloads are always gzip-compressed to reduce bandwidth usage.                                                                                                      |
| `User-Agent`         | `Catena-Webhook`                       | Identifies requests as coming from Catena webhooks.                                                                                                                                                   |
| `Accept`             | `application/json`                     | Expected response format.                                                                                                                                                                             |

<Tip>
  **Duplicate Detection:** Store processed `X-Request-ID` values to detect and skip duplicate deliveries during retries.
</Tip>

***

## Signature Verification

Catena signs every webhook using **HMAC-SHA256** to ensure authenticity. The signature covers the timestamp and request body, preventing tampering and replay attacks.

### Signing Key Management

When creating a webhook subscription, you can either provide your own signing key or let Catena generate one:

<AccordionGroup>
  <Accordion title="Provide Your Own Key" icon="key">
    Include your secret key in the subscription request for full control over key management.
  </Accordion>

  <Accordion title="Catena-Generated Key" icon="sparkles">
    Omit the `secret` field and Catena will generate a secure key returned **once** in the creation response.

    The generated secret is shown in full only in the initial creation response. In all subsequent API responses, the secret is masked as `*****` for security.

    <Warning>
      **Store Securely:** The generated secret is shown only once. Store it securely in your secrets manager.
    </Warning>
  </Accordion>

  <Accordion title="Key Rotation" icon="rotate">
    If you lose your key or need to rotate it for security, use the webhook update endpoint to set a new secret.
  </Accordion>

  <Accordion title="Lost Your Secret?" icon="circle-question">
    If you've lost your signing secret, you cannot retrieve it — it's masked as `*****` in all API responses after creation. To recover, rotate to a new secret using the update endpoint:

    ```bash theme={null}
      curl -X PATCH \
        --url https://api.catenatelematics.com/v2/notifications/webhooks/<webhook_id> \
        -H 'Authorization: Bearer <token>' \
        -H 'Content-Type: application/json' \
        -d '{"secret": "your-new-secret-min-12-chars"}'
    ```

    Update your endpoint to use the new secret immediately after — deliveries will begin failing signature verification as soon as the rotation takes effect.
  </Accordion>
</AccordionGroup>

### How Signatures Work

Each webhook includes two security headers:

* **`X-Catena-Signature`** — Base64-encoded HMAC-SHA256 digest
* **`X-Catena-Timestamp`** — RFC 3339 timestamp (e.g., `2024-01-15T10:30:00+00:00`)

The signature is computed as:

```
Base64(HMAC-SHA256(secret, timestamp + "." + uncompressed_payload))
```

***

## Verification Steps

Implement signature verification in four steps:

<Steps>
  <Step title="Extract headers and body">
    Get the signature headers and raw request body:

    * `X-Catena-Timestamp` — The timestamp used in signature computation
    * `X-Catena-Signature` — The expected signature
    * Uncompressed payload — Decompress the gzipped request body to get the JSON string

    <Warning>
      **Decompress First:** Webhooks are always sent with `Content-Encoding: gzip`. You must decompress the raw request bytes to get the JSON string used for signature verification.
    </Warning>
  </Step>

  <Step title="Check timestamp freshness">
    Parse the timestamp and verify it's recent:

    * Parse `X-Catena-Timestamp` as RFC 3339 / ISO 8601
    * Reject requests older than **5 minutes** to prevent replay attacks
    * Account for clock skew between servers
  </Step>

  <Step title="Compute expected signature">
    Recreate the signature using your webhook secret:

    ```
    signed_payload = timestamp + "." + uncompressed_payload
    expected_signature = Base64(HMAC-SHA256(secret, signed_payload))
    ```
  </Step>

  <Step title="Compare signatures securely">
    Use constant-time comparison to prevent timing attacks:

    * Compare `X-Catena-Signature` with your computed signature
    * Use timing-safe comparison (e.g., `hmac.compare_digest` in Python)
    * Respond with **401 Unauthorized** if verification fails
    * Respond with **400 Bad Request** if headers are missing/malformed
  </Step>
</Steps>

***

## Implementation Example

```python Python theme={null}
import base64
import hashlib
import hmac
import gzip
from datetime import datetime, timezone, timedelta


def verify_catena_webhook(headers: dict, raw_body: bytes, secret: str) -> bool:
    """
    Verify a Catena webhook signature.

    Args:
        headers: Request headers dict
        raw_body: Raw request body as bytes (potentially compressed)
        secret: Your webhook secret

    Returns:
        True if signature is valid and timestamp is fresh
    """
    timestamp = headers.get("X-Catena-Timestamp")
    signature = headers.get("X-Catena-Signature")

    if not timestamp or not signature:
        return False

    # Check timestamp freshness (within 5 minutes)
    try:
        ts = datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
        age = datetime.now(timezone.utc) - ts
        if age > timedelta(minutes=5):
            return False
    except ValueError:
        return False

    # Decompress payload (always gzip)
    try:
        payload = gzip.decompress(raw_body).decode("utf-8")
    except Exception:
        return False

    # Compute expected signature
    signed_payload = f"{timestamp}.{payload}".encode()
    expected = base64.b64encode(
        hmac.new(secret.encode(), signed_payload, hashlib.sha256).digest()
    ).decode()

    # Constant-time comparison
    return hmac.compare_digest(signature, expected)


# Usage in a Flask endpoint
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = "your-webhook-secret"

@app.route("/webhooks/catena", methods=["POST"])
def handle_webhook():
    # Get raw body before Flask parses it
    raw_body = request.get_data()

    if not verify_catena_webhook(request.headers, raw_body, WEBHOOK_SECRET):
        return jsonify({"error": "Invalid signature"}), 401

    # Process the webhook
    event = request.get_json()
    event_type = request.headers.get("X-Event-Type")

    # Handle the event...
    return jsonify({"status": "received"}), 200
```

***

## Security Best Practices

<CardGroup cols={2}>
  <Card title="Always verify signatures" icon="shield-check">
    Never process webhook events without signature verification. This protects against spoofing and tampering.
  </Card>

  <Card title="Use constant-time comparison" icon="clock">
    Prevent timing attacks by using constant-time comparison functions (`hmac.compare_digest`, `crypto.timingSafeEqual`).
  </Card>

  <Card title="Reject stale requests" icon="calendar-xmark">
    Check timestamp freshness and reject requests older than 5 minutes to prevent replay attacks.
  </Card>

  <Card title="Always decompress payload" icon="file-code">
    Webhooks are always gzip-compressed. Decompress the raw bytes to get the JSON string for signature verification.
  </Card>

  <Card title="Store secrets securely" icon="vault">
    Keep webhook secrets in environment variables or a secrets manager, never in code.
  </Card>

  <Card title="Implement rate limiting" icon="gauge-high">
    Protect your endpoint from abuse with rate limiting, even for authenticated requests.
  </Card>

  <Card title="Log validation failures" icon="clipboard-list">
    Monitor and alert on signature validation failures to detect potential attacks.
  </Card>

  <Card title="Use HTTPS only" icon="lock">
    Configure webhook URLs with HTTPS to encrypt data in transit. Catena rejects HTTP endpoints.
  </Card>
</CardGroup>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Signature verification always fails" icon="circle-xmark">
    **Common causes:**

    * Using parsed JSON instead of uncompressed payload string
    * Not decompressing the payload before verification
    * Incorrect webhook secret
    * Character encoding issues (ensure UTF-8)

    **Solution:** Ensure you're using the uncompressed JSON string. Decompress the raw gzipped bytes first.
  </Accordion>

  <Accordion title="Intermittent verification failures" icon="triangle-exclamation">
    **Common causes:**

    * Clock skew between servers
    * Timestamp tolerance too strict
    * Network delays causing stale timestamps

    **Solution:** Allow a 5-minute tolerance window and ensure your server clock is synchronized via NTP.
  </Accordion>

  <Accordion title="Missing headers" icon="question">
    **Common causes:**

    * Proxy or load balancer stripping headers
    * Case-sensitive header lookups
    * Framework normalizing header names

    **Solution:** Check your infrastructure configuration. Headers may be lowercase (`x-catena-signature`) depending on your framework.
  </Accordion>
</AccordionGroup>

***

## What's Next

<CardGroup cols={2}>
  <Card title="Webhook Quick Start" icon="bolt" href="/api-reference/webhooks/webhook-setup">
    Create your first subscription and start receiving live fleet events.
  </Card>

  <Card title="Operations & Monitoring" icon="chart-line" href="/api-reference/webhooks/operations-monitoring">
    Monitor delivery health, replay failed events, and manage the webhook lifecycle.
  </Card>

  <Card title="FAQ" icon="circle-question" href="/api-reference/webhooks/webhook-faq">
    Answers to common questions about webhook behavior and troubleshooting.
  </Card>

  <Card title="Notifications API Reference" icon="code" href="/api-reference/notifications-api">
    Complete API reference for all webhook endpoints.
  </Card>
</CardGroup>

***

<Card icon="life-ring" title="Contact Support" href="mailto:support@catenaclearing.io">
  Have questions or need assistance? Our team is here to help you succeed.
</Card>
