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

# Verification

> How to verify webhook requests

## Overview

HookStack signs every webhook request to ensure the authenticity and integrity of the payload.
This guide explains how to verify these signatures in your webhook handlers.

## Understanding the Signature

Each webhook request from HookStack includes several security-related headers:

```plaintext
X-HookStack-Version: v1.0
X-HookStack-RequestId: <unique-request-id>
X-HookStack-Timestamp: <timestamp>
X-HookStack-Signature: <signature>
```

The signature is a base64-encoded HMAC SHA-256 hash generated using:

1. The timestamp from the `X-HookStack-Timestamp` header
2. The version from the `X-HookStack-Version` header
3. The complete JSON-stringified payload

## Verifying the Signature

Here's how to verify the signature in your webhook handler:

```typescript verifyWebhookSignature.ts
function verifyWebhookSignature(
  payload: unknown,
  headers: {
    'x-hookstack-timestamp': string,
    'x-hookstack-version': string,
    'x-hookstack-signature': string
  },
  signingSecret: string
): boolean {
  const timestamp = headers['x-hookstack-timestamp']
  const version = headers['x-hookstack-version']
  const expectedSignature = headers['x-hookstack-signature']

  // Construct the string to sign
  const stringToSign = `${timestamp}:${version}:${JSON.stringify(payload)}`

  // Generate the signature
  const signature = crypto
    .createHmac('sha256', signingSecret)
    .update(stringToSign)
    .digest('base64')

  // Compare signatures using a timing-safe comparison
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  )
}
```

<Note>
  Using timing-safe comparison when verifying signatures, or checking the timestamp against the
  current time helps prevent timing attacks.
</Note>

## Best Practices

<AccordionGroup>
  <Accordion title="Verify Every Request" icon="shield-check">
    Always verify the signature before processing any webhook payload
  </Accordion>

  <Accordion title="Reply Quickly" icon="bolt">
    HookStack requires a response to the webhook request within 7 seconds.
    No response will be interpreted as a failed request (504 timeout). Use background processing tasks
    to handle webhook processing after verifying the signature and sending a response.
  </Accordion>

  <Accordion title="Check Timestamp" icon="clock">
    Verify the timestamp is recent (within 5 minutes) to prevent replay attacks
  </Accordion>

  <Accordion title="Use Environment Variables" icon="user-secret">
    Store your signing secret in environment variables, never in code
  </Accordion>

  <Accordion title="Handle Errors Gracefully" icon="rotate-exclamation">
    Return appropriate HTTP status codes for verification failures;
    this allows HookStack to retry the request.
  </Accordion>
</AccordionGroup>

Example implementation with all best practices:

```typescript
import { timingSafeEqual } from 'crypto'

function handleWebhook(req: Request, res: Response) {
  const signature = req.headers['x-hookstack-signature']
  const timestamp = Number(req.headers['x-hookstack-timestamp'])
  const version = req.headers['x-hookstack-version']
  const payload = req.body

  // 1. Verify all required headers are present
  if (!signature || !timestamp || !version) {
    return res.status(400).json({ error: 'Missing required headers' })
  }

  // 2. Check timestamp is recent (within 5 minutes)
  const fiveMinutesAgo = Date.now() - 5 * 60 * 1000
  if (timestamp < fiveMinutesAgo) {
    return res.status(400).json({ error: 'Request has expired' })
  }

  // 3. Verify signature
  try {
    const isValid = verifyWebhookSignature(
      payload,
      {
        'x-hookstack-timestamp': timestamp.toString(),
        'x-hookstack-version': version,
        'x-hookstack-signature': signature
      },
      process.env.HOOKSTACK_SIGNING_SECRET!
    )

    if (!isValid) {
      return res.status(401).json({ error: 'Invalid signature' })
    }

    // Process the webhook...
    return res.status(200).json({ received: true })
  } catch (error) {
    console.error('Webhook verification failed:', error)
    return res.status(500).json({ error: 'Verification failed' })
  }
}
```

<Note>
  The signing secret is provided in your HookStack dashboard under each Destination.

  It is required for HTTP-type Destinations only.
</Note>

## Testing Verification

HookStack provides test endpoints and signing secrets in the dashboard to help you verify your implementation:

1. Use the test webhook feature in the dashboard
2. Check the test logs to see detailed request/response information
3. Verify your error handling by sending invalid signatures

<Tip>
  Use our [SDK](/sdks/typescript) to automatically handle webhook verification with built-in security best practices.
</Tip>
