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

# Webhook Verification

> Verify Pro Trading API webhook authenticity using HMAC signatures

## Overview

All Daya Pro webhooks include an `X-Webhook-Signature` header containing an HMAC-SHA256 signature of the payload with a `sha256=` prefix. **Always verify** this signature to ensure the webhook came from Daya.

<Warning>
  Never process unverified webhooks. Attackers could send fake webhooks to manipulate your trading system.
</Warning>

## Signature Header

```
X-Webhook-Signature: sha256=a8f5f167f44f4964e6c998dee827110c447be52d40d67b6a60b78c1e3e01b7e8
```

## Additional Headers

Daya also sends these headers with every webhook request:

| Header                | Description                       |
| --------------------- | --------------------------------- |
| `X-Webhook-Event`     | Event type (e.g., `order.filled`) |
| `X-Webhook-ID`        | Unique event identifier (UUID)    |
| `X-Webhook-Timestamp` | Event timestamp (RFC3339)         |
| `User-Agent`          | `Daya-Webhook/1.0`                |

## Verification Algorithm

1. Get raw request body as string
2. Compute HMAC-SHA256 using your webhook secret
3. Strip the `sha256=` prefix from `X-Webhook-Signature` header
4. Compare computed signature with the extracted signature
5. Use timing-safe comparison to prevent timing attacks

## Implementation Examples

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

  function verifyWebhookSignature(payload, signature, secret) {
    // Compute expected signature
    const expectedSignature = crypto
      .createHmac('sha256', secret)
      .update(payload, 'utf8')
      .digest('hex');

    // Timing-safe comparison
    try {
      return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expectedSignature)
      );
    } catch (error) {
      return false;
    }
  }

  // Express.js middleware
  app.post('/webhooks/daya-pro', express.raw({ type: 'application/json' }), (req, res) => {
    const signatureHeader = req.headers['x-webhook-signature'] || '';
    const signature = signatureHeader.replace('sha256=', ''); // Strip prefix
    const payload = req.body.toString('utf8');

    if (!verifyWebhookSignature(payload, signature, process.env.DAYA_PRO_WEBHOOK_SECRET)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    // Process webhook...
    const event = JSON.parse(payload);
    res.status(200).send('OK');
  });
  ```

  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
      """Verify webhook HMAC signature"""
      expected_signature = hmac.new(
          secret.encode('utf-8'),
          payload,
          hashlib.sha256
      ).hexdigest()

      # Timing-safe comparison
      return hmac.compare_digest(signature, expected_signature)

  # Flask example
  from flask import Flask, request, jsonify

  @app.route('/webhooks/daya-pro', methods=['POST'])
  def handle_webhook():
      signature_header = request.headers.get('X-Webhook-Signature', '')
      signature = signature_header.replace('sha256=', '')  # Strip prefix
      payload = request.get_data()

      if not verify_webhook_signature(payload, signature, DAYA_PRO_WEBHOOK_SECRET):
          return jsonify({'error': 'Invalid signature'}), 401

      # Process webhook...
      event = request.json
      return jsonify({'status': 'ok'}), 200
  ```

  ```go Go theme={null}
  package main

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

  func verifyWebhookSignature(payload []byte, signature string, secret string) bool {
      mac := hmac.New(sha256.New, []byte(secret))
      mac.Write(payload)
      expectedSignature := hex.EncodeToString(mac.Sum(nil))
      return hmac.Equal([]byte(signature), []byte(expectedSignature))
  }

  func handleWebhook(w http.ResponseWriter, r *http.Request) {
      signatureHeader := r.Header.Get("X-Webhook-Signature")
      signature := strings.TrimPrefix(signatureHeader, "sha256=") // Strip prefix
      payload, _ := io.ReadAll(r.Body)

      if !verifyWebhookSignature(payload, signature, dayaProWebhookSecret) {
          http.Error(w, "Invalid signature", http.StatusUnauthorized)
          return
      }

      // Process webhook...
      w.WriteHeader(http.StatusOK)
  }
  ```

  ```php PHP theme={null}
  <?php

  function verifyWebhookSignature($payload, $signature, $secret) {
      $expectedSignature = hash_hmac('sha256', $payload, $secret);
      return hash_equals($signature, $expectedSignature);
  }

  // Handle webhook
  $signatureHeader = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
  $signature = str_replace('sha256=', '', $signatureHeader); // Strip prefix
  $payload = file_get_contents('php://input');

  if (!verifyWebhookSignature($payload, $signature, $webhookSecret)) {
      http_response_code(401);
      echo json_encode(['error' => 'Invalid signature']);
      exit;
  }

  // Process webhook...
  $event = json_decode($payload, true);
  http_response_code(200);
  echo json_encode(['status' => 'ok']);
  ```
</CodeGroup>

## Important Notes

<AccordionGroup>
  <Accordion title="Use raw request body">
    **Critical:** Compute HMAC on the **raw request body** before parsing JSON. Parsing changes whitespace and ordering, breaking the signature.

    ```javascript theme={null}
    // Correct: Use raw body
    app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
      const payload = req.body.toString('utf8');
      verify(payload, signature, secret);
    });

    // Wrong: JSON.stringify changes format
    app.post('/webhooks', express.json(), (req, res) => {
      const payload = JSON.stringify(req.body); // Wrong!
      verify(payload, signature, secret);
    });
    ```
  </Accordion>

  <Accordion title="Use timing-safe comparison">
    Regular string comparison (==) is vulnerable to timing attacks. Use constant-time comparison:

    * Node.js: `crypto.timingSafeEqual()`
    * Python: `hmac.compare_digest()`
    * Go: `hmac.Equal()`
    * PHP: `hash_equals()`
  </Accordion>

  <Accordion title="Keep secrets secure">
    * Store webhook secret in environment variables
    * Never commit secrets to version control
    * Rotate secrets regularly
    * Use different secrets for different environments
  </Accordion>
</AccordionGroup>

## Testing Verification

Generate test signatures for local testing:

<CodeGroup>
  ```bash CLI theme={null}
  # Generate test signature
  echo -n '{"event":"order.filled","event_id":"evt_pro_test"}' | \
    openssl dgst -sha256 -hmac "your_webhook_secret" | \
    awk '{print $2}'
  ```

  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function generateTestSignature(payload, secret) {
    return crypto
      .createHmac('sha256', secret)
      .update(payload, 'utf8')
      .digest('hex');
  }

  const payload = '{"event":"order.filled","event_id":"evt_pro_test"}';
  const signature = generateTestSignature(payload, 'your_webhook_secret');
  console.log(signature);
  ```
</CodeGroup>

## Common Issues

<AccordionGroup>
  <Accordion title="Signature always fails">
    **Possible causes:**

    * Using wrong webhook secret
    * Not using raw request body
    * Character encoding issues

    **Debug:**

    ```javascript theme={null}
    console.log('Received signature:', signature);
    console.log('Expected signature:', expectedSignature);
    console.log('Payload length:', payload.length);
    console.log('Secret (first 4 chars):', secret.substring(0, 4));
    ```
  </Accordion>

  <Accordion title="Intermittent failures">
    **Cause:** Parsing JSON before verification

    **Fix:** Always compute HMAC on raw body, then parse JSON
  </Accordion>

  <Accordion title="Different signatures on retry">
    **Cause:** This shouldn't happen - same payload = same signature

    **Debug:** Log and compare payloads between requests
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Webhook Overview" icon="webhook" href="/pro/webhooks/overview">
    Delivery guarantees and implementation
  </Card>

  <Card title="Webhook Events" icon="bell" href="/pro/webhooks/events">
    Event schemas and payloads
  </Card>
</CardGroup>
