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

# Webhooks

> Listen to events from the Steuerboard API.

## Overview

Webhooks enable your application to set up event based actions. In this section, you’ll learn how to configure webhooks to receive updates from Steuerboard.

## Events

* **File**: A file has been created, updated or deleted.
* **File Comment**: A file comment has been created
* **Task**: A task has been created, updated or deleted.
* **Task Comment**: A task comment has been created
* **Client**: A client has been created, updated or deleted.
* **Workspace**: A workspace has been created, updated or deleted.

## Configuration

To configure webhooks, you need to create an endpoint in your Settings.

<Steps>
  <Step title="Visit Dashboard">
    Visit your [Steuerboard Dashboard](https://app.steuerboard.com).

    **Don't have an account?**

    <Note>We offer a free plan for testing. Just mail us at [founders@steuerboard.com](mailto:founders@steuerboard.com)</Note>

    If you want to use our App in production, please take a look at our [Pricing page](https://steuerboard.com/pricing) to book a call with us.
  </Step>

  <Step title="Create Webhook">
    Go to **Settings -> API** and click on **"Create Webhook"**.
  </Step>

  <Step title="Configure">
    Enter a valid URL and select the events you want to receive.
  </Step>
</Steps>

## Retries

Webhooks are retried 7 times with an exponential backoff. If the webhook fails 7 times, the endpoint will be disabled. You can re-enable the endpoint at any time in your dashboard.

## Webhook Authentication

Webhook authentication ensures that incoming webhook requests are securely verified before processing. This allows consumers to trust that webhook events originate from a secure and verified source.

### How It Works

Each webhook request sent from the server includes an `X-Webhook-Signature` header containing a SHA-256 HMAC signature of the request payload. This signature is generated using a secret key known only to the server and your application.
When the consumer receives a webhook, they can use the signature provided in the `X-Webhook-Signature` header to verify that the request has not been tampered with. This is done by computing their own HMAC signature using the shared secret key and comparing it to the signature included in the header.

### Verifying the Signature

* Compute the HMAC SHA-256 signature using the payload and the shared secret key
* Compare the computed signature to the `X-Webhook-Signature` header value
* If they match, the request is verified as authentic. If they do not match, treat the request with caution or reject it

By verifying webhook signatures, consumers can ensure that webhook events received are secure and have not been altered during transmission.

### Code Examples

Here's how to verify webhook signatures in different programming languages:

<CodeGroup dropdown>
  ```javascript handler.js theme={null}
  const crypto = require('crypto');

  function verifyWebhookSignature(payload, signature, secret) {
    // Parse the signature header
    const elements = signature.split(',');
    const sigData = {};
    
    for (const element of elements) {
      const [key, value] = element.split('=');
      sigData[key] = value;
    }
    
    // Extract timestamp and signature
    const timestamp = sigData.t;
    const expectedSignature = sigData.v1;
    
    if (!timestamp || !expectedSignature) {
      throw new Error('Invalid signature format');
    }
    
    // Create the payload string that was signed
    const signedPayload = `${timestamp}.${payload}`;
    
    // Compute the HMAC
    const computedSignature = crypto
      .createHmac('sha256', secret)
      .update(signedPayload)
      .digest('hex');
    
    // Compare signatures using a constant-time comparison
    return crypto.timingSafeEqual(
      Buffer.from(expectedSignature, 'hex'),
      Buffer.from(computedSignature, 'hex')
    );
  }

  // Express.js example
  app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
    const signature = req.headers['x-webhook-signature'];
    const payload = req.body.toString();
    const secret = process.env.WEBHOOK_SECRET; // Your webhook secret
    
    try {
      if (!verifyWebhookSignature(payload, signature, secret)) {
        return res.status(401).send('Invalid signature');
      }
      
      // Process the webhook
      const data = JSON.parse(payload);
      console.log('Verified webhook:', data);
      
      res.status(200).send('OK');
    } catch (error) {
      console.error('Webhook verification failed:', error);
      res.status(400).send('Bad request');
    }
  });
  ```

  ```python handler.py theme={null}
  import hmac
  import hashlib
  import os
  import time

  def verify_webhook_signature(payload: str, signature: str, secret: str) -> bool:
      """Verify webhook signature"""
      
      # Parse the signature header
      sig_data = {}
      for element in signature.split(','):
          key, value = element.split('=', 1)
          sig_data[key] = value
      
      timestamp = sig_data.get('t')
      expected_signature = sig_data.get('v1')
      
      if not timestamp or not expected_signature:
          raise ValueError('Invalid signature format')
      
      # Create the payload string that was signed
      signed_payload = f"{timestamp}.{payload}"
      
      # Compute the HMAC
      computed_signature = hmac.new(
          secret.encode('utf-8'),
          signed_payload.encode('utf-8'),
          hashlib.sha256
      ).hexdigest()
      
      # Compare signatures using a constant-time comparison
      return hmac.compare_digest(expected_signature, computed_signature)

  # Flask example
  from flask import Flask, request, jsonify

  app = Flask(__name__)

  @app.route('/webhook', methods=['POST'])
  def handle_webhook():
      signature = request.headers.get('X-Webhook-Signature')
      payload = request.get_data(as_text=True)
      secret = os.environ.get('WEBHOOK_SECRET')  # Your webhook secret
      
      try:
          if not verify_webhook_signature(payload, signature, secret):
              return jsonify({'error': 'Invalid signature'}), 401
          
          # Process the webhook
          data = request.get_json()
          print(f'Verified webhook: {data}')
          
          return jsonify({'status': 'success'}), 200
      except Exception as e:
          print(f'Webhook verification failed: {e}')
          return jsonify({'error': 'Bad request'}), 400
  ```

  ```go handler.go theme={null}
  package main

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

  func verifyWebhookSignature(payload, signature, secret string) (bool, error) {
      // Parse the signature header
      sigData := make(map[string]string)
      for _, element := range strings.Split(signature, ",") {
          parts := strings.SplitN(element, "=", 2)
          if len(parts) == 2 {
              sigData[parts[0]] = parts[1]
          }
      }
      
      timestamp, ok := sigData["t"]
      if !ok {
          return false, fmt.Errorf("missing timestamp in signature")
      }
      
      expectedSignature, ok := sigData["v1"]
      if !ok {
          return false, fmt.Errorf("missing signature in header")
      }
      
      // Create the payload string that was signed
      signedPayload := fmt.Sprintf("%s.%s", timestamp, payload)
      
      // Compute the HMAC
      mac := hmac.New(sha256.New, []byte(secret))
      mac.Write([]byte(signedPayload))
      computedSignature := hex.EncodeToString(mac.Sum(nil))
      
      // Compare signatures using constant-time comparison
      return subtle.ConstantTimeCompare(
          []byte(expectedSignature),
          []byte(computedSignature),
      ) == 1, nil
  }

  func webhookHandler(w http.ResponseWriter, r *http.Request) {
      signature := r.Header.Get("X-Webhook-Signature")
      
      // Read the body
      body, err := io.ReadAll(r.Body)
      if err != nil {
          http.Error(w, "Failed to read request body", http.StatusBadRequest)
          return
      }

      payload := string(body)
      
      secret := os.Getenv("WEBHOOK_SECRET") // Your webhook secret
      
      valid, err := verifyWebhookSignature(payload, signature, secret)
      if err != nil {
          http.Error(w, "Bad request", http.StatusBadRequest)
          return
      }
      
      if !valid {
          http.Error(w, "Invalid signature", http.StatusUnauthorized)
          return
      }
      
      // Process the webhook
      fmt.Printf("Verified webhook: %s\n", payload)
      w.WriteHeader(http.StatusOK)
  }
  ```
</CodeGroup>

### Signature Format

The `X-Webhook-Signature` header contains multiple components separated by commas:

* `t=<timestamp>`: Unix timestamp when the signature was generated
* `v1=<signature>`: HMAC-SHA256 signature in hexadecimal format
* `alg=<algorithm>`: The algorithm used (always `hmac-sha256`)

Example: `t=1640995200,v1=a1b2c3d4...,alg=hmac-sha256`
