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

# Webhook Validation

> Secure webhook signature validation for Rise B2B API

<Note>
  Webhook validation is crucial for ensuring the security and authenticity of incoming webhook events from Rise B2B API.
</Note>

## Webhook Security Overview

Webhooks provide real-time notifications but must be validated to ensure they come from Rise and haven't been tampered with. Our webhook validation uses HMAC-SHA256 signatures for security.

<CardGroup cols={2}>
  <Card title="Signature Verification" icon="shield">
    * HMAC-SHA256 signatures
    * Timestamp validation
    * Replay attack prevention
    * Tamper detection
  </Card>

  <Card title="Security Benefits" icon="lock">
    * Authentic source verification
    * Data integrity assurance
    * Attack prevention
    * Compliance requirements
  </Card>
</CardGroup>

## Webhook Signature Format

Rise sends webhooks with a signature header in this format:

```
Rise-Signature: t=1705312200,v1=abc123def456...
```

Where:

* `t` = Unix timestamp
* `v1` = HMAC-SHA256 signature

## Using the Webhook Validator

### Basic Validation

```typescript theme={null}
import { WebhookValidator } from '@riseworks/riseworks-sdk';

// Initialize validator with your webhook secret
const validator = new WebhookValidator({
  secret: process.env.WEBHOOK_SECRET
});

// Express.js webhook endpoint
app.post('/webhooks/rise', (req, res) => {
  try {
    // Validate the webhook signature
    const isValid = validator.validateEvent(
      req.body,
      req.headers['rise-signature']
    );
    
    if (isValid) {
      // Process the webhook
      console.log('Webhook validated:', req.body);
      res.status(200).json({ received: true });
    } else {
      res.status(400).json({ error: 'Invalid signature' });
    }
  } catch (error) {
    console.error('Webhook validation error:', error);
    res.status(400).json({ error: error.message });
  }
});
```

### Safe Validation (Returns Result)

```typescript theme={null}
import { WebhookValidator } from '@riseworks/riseworks-sdk';

const validator = new WebhookValidator({
  secret: process.env.WEBHOOK_SECRET
});

app.post('/webhooks/rise', (req, res) => {
  // Use safe validation that returns a result object
  const result = validator.validateEventSafe(
    req.body,
    req.headers['rise-signature']
  );
  
  if (result.valid) {
    // Process webhook
    console.log('Webhook processed:', req.body);
    res.status(200).json({ received: true });
  } else {
    console.error('Webhook validation failed:', result.error);
    res.status(400).json({ error: result.error });
  }
});
```

## Manual Validation

### Parse Signature Header

```typescript theme={null}
import { WebhookValidator } from '@riseworks/riseworks-sdk';

const validator = new WebhookValidator({
  secret: process.env.WEBHOOK_SECRET
});

// Parse signature header manually
const signatureHeader = req.headers['rise-signature'];
const { timestamp, signature } = validator.parseSignatureHeader(signatureHeader);

console.log('Timestamp:', timestamp);
console.log('Signature:', signature);
```

### Validate Timestamp

```typescript theme={null}
// Check if webhook is within acceptable time range
const tolerance = 300; // 5 minutes
const now = Math.floor(Date.now() / 1000);

if (Math.abs(now - timestamp) > tolerance) {
  throw new Error('Webhook timestamp too old');
}
```

### Compare Signatures

```typescript theme={null}
// Generate expected signature
const expectedSignature = validator.generateSignature(req.body, timestamp);

// Compare signatures securely
const isValid = validator.compareSignatures(signature, expectedSignature);

if (isValid) {
  console.log('Signature verified');
} else {
  console.log('Signature verification failed');
}
```

## Complete Validation Example

```typescript theme={null}
import { WebhookValidator } from '@riseworks/riseworks-sdk';
import express from 'express';

const app = express();
app.use(express.json());

const validator = new WebhookValidator({
  secret: process.env.WEBHOOK_SECRET,
  tolerance: 300 // 5 minutes
});

app.post('/webhooks/rise', (req, res) => {
  try {
    // Validate webhook
    const isValid = validator.validateEvent(
      req.body,
      req.headers['rise-signature']
    );
    
    if (!isValid) {
      return res.status(400).json({ error: 'Invalid webhook signature' });
    }
    
    // Process webhook based on event type
    const { event, data } = req.body;
    
    switch (event) {
      case 'payment.completed':
        handlePaymentCompleted(data);
        break;
      case 'payment.failed':
        handlePaymentFailed(data);
        break;
      case 'invite.accepted':
        handleInviteAccepted(data);
        break;
      case 'invite.rejected':
        handleInviteRejected(data);
        break;
      case 'invite.expired':
        handleInviteExpired(data);
        break;
      default:
        console.log('Unhandled event:', event);
    }
    
    res.status(200).json({ received: true });
  } catch (error) {
    console.error('Webhook processing error:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

function handlePaymentCompleted(data) {
  console.log('Payment completed:', data.payment_id);
  // Update database, send notifications, etc.
}

function handlePaymentFailed(data) {
  console.log('Payment failed:', data.payment_id);
  // Handle failed payment
}

function handleInviteAccepted(data) {
  console.log('Invite accepted:', data.invite_id);
  // Update team member status
}

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});
```

## Error Handling

### Common Validation Errors

```typescript theme={null}
try {
  const isValid = validator.validateEvent(req.body, req.headers['rise-signature']);
} catch (error) {
  switch (error.message) {
    case 'Missing signature header':
      console.error('No Rise-Signature header found');
      break;
    case 'Invalid signature format':
      console.error('Signature header format is invalid');
      break;
    case 'Webhook timestamp too old':
      console.error('Webhook timestamp is outside tolerance window');
      break;
    case 'Invalid signature':
      console.error('Signature verification failed');
      break;
    default:
      console.error('Unknown validation error:', error.message);
  }
}
```

### Logging and Monitoring

```typescript theme={null}
// Webhook validation metrics
const webhookMetrics = {
  total: 0,
  valid: 0,
  invalid: 0,
  errors: []
};

app.post('/webhooks/rise', (req, res) => {
  webhookMetrics.total++;
  
  try {
    const isValid = validator.validateEvent(
      req.body,
      req.headers['rise-signature']
    );
    
    if (isValid) {
      webhookMetrics.valid++;
      // Process webhook
    } else {
      webhookMetrics.invalid++;
      res.status(400).json({ error: 'Invalid signature' });
      return;
    }
  } catch (error) {
    webhookMetrics.errors.push({
      timestamp: new Date().toISOString(),
      error: error.message
    });
    res.status(400).json({ error: error.message });
    return;
  }
  
  res.status(200).json({ received: true });
});

// Log metrics periodically
setInterval(() => {
  console.log('Webhook metrics:', webhookMetrics);
}, 60000); // Every minute
```

## Security Best Practices

### Environment Configuration

```bash theme={null}
# .env file
WEBHOOK_SECRET=your_webhook_secret_here
WEBHOOK_TOLERANCE=300
```

### Validation Configuration

```typescript theme={null}
const validator = new WebhookValidator({
  secret: process.env.WEBHOOK_SECRET,
  tolerance: parseInt(process.env.WEBHOOK_TOLERANCE || '300')
});
```

### Security Checklist

<Steps>
  <Step title="Secret Management">
    Store webhook secret securely
    Use environment variables
    Never commit secret to version control
    Rotate secrets regularly
  </Step>

  <Step title="Validation">
    Validate all incoming webhooks
    Check timestamp tolerance
    Verify signature format
    Handle validation errors
  </Step>

  <Step title="Monitoring">
    Log validation failures
    Monitor webhook activity
    Set up alerts for suspicious activity
    Track validation metrics
  </Step>

  <Step title="Error Handling">
    Return appropriate HTTP status codes
    Log detailed error information
    Implement retry logic for failures
    Monitor error rates
  </Step>
</Steps>

## Testing Webhook Validation

### Test with Sample Data

```typescript theme={null}
// Test webhook validation
const testWebhook = {
  event: 'payment.completed',
  data: {
    payment_id: 'pay_123456789',
    amount: '1000.00',
    currency: 'USD'
  },
  timestamp: Math.floor(Date.now() / 1000)
};

// Generate test signature
const testSignature = validator.generateSignature(
  testWebhook,
  testWebhook.timestamp
);

// Test validation
const isValid = validator.validateEvent(testWebhook, `t=${testWebhook.timestamp},v1=${testSignature}`);
console.log('Test validation result:', isValid);
```

### Unit Tests

```typescript theme={null}
import { WebhookValidator } from '@riseworks/riseworks-sdk';

describe('WebhookValidator', () => {
  let validator;
  
  beforeEach(() => {
    validator = new WebhookValidator({
      secret: 'test-secret'
    });
  });
  
  test('should validate correct signature', () => {
    const payload = { event: 'test', data: {} };
    const timestamp = Math.floor(Date.now() / 1000);
    const signature = validator.generateSignature(payload, timestamp);
    const signatureHeader = `t=${timestamp},v1=${signature}`;
    
    const isValid = validator.validateEvent(payload, signatureHeader);
    expect(isValid).toBe(true);
  });
  
  test('should reject invalid signature', () => {
    const payload = { event: 'test', data: {} };
    const signatureHeader = 't=1234567890,v1=invalid-signature';
    
    const isValid = validator.validateEvent(payload, signatureHeader);
    expect(isValid).toBe(false);
  });
  
  test('should reject old timestamp', () => {
    const payload = { event: 'test', data: {} };
    const timestamp = Math.floor(Date.now() / 1000) - 600; // 10 minutes ago
    const signature = validator.generateSignature(payload, timestamp);
    const signatureHeader = `t=${timestamp},v1=${signature}`;
    
    const isValid = validator.validateEvent(payload, signatureHeader);
    expect(isValid).toBe(false);
  });
});
```

## Next Steps

1. **[Security Overview](./overview)** - Complete security architecture
2. **[Secondary Wallets](./secondary-wallets)** - Using dedicated wallets
3. **[Best Practices](./best-practices)** - Security best practices
