npm package: @inforbit/permutapp
Description: Official PermutApp webhook SDK for Node.js
Author: PermutApp Team
Latest version: 1.0.7

# PermutApp SDK for Node.js

Official Node.js SDK for handling PermutApp webhooks with signature verification.

## Installation

```bash
npm install @inforbitstudio/permutapp
```

## Usage

### Basic Example

```javascript
const express = require('express');
const { permutapp } = require('@inforbitstudio/permutapp');

const app = express();

app.use('/webhook', express.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const secret = 'whsec_your_webhook_secret';

  try {
    const event = permutapp.constructEvent(req.body, signature, secret);

    switch (event.type) {
      case 'request.created':
        console.log('New request created:', event.data);
        break;
      case 'request.evaluated':
        console.log('Request evaluated:', event.data);
        break;
    }

    res.json({ received: true });
  } catch (err) {
    console.log('Webhook signature verification failed:', err.message);
    res.status(400).send(`Webhook Error: ${err.message}`);
  }
});
```

### TypeScript Example

```typescript
import express from 'express';
import {
  permutapp,
  WebhookEvent,
  WebhookSignatureVerificationError,
} from '@inforbitstudio/permutapp';

const app = express();
app.use('/webhook', express.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-webhook-signature'] as string;
  const secret = 'whsec_your_webhook_secret';

  try {
    const event: WebhookEvent = permutapp.constructEvent(
      req.body,
      signature,
      secret
    );

    switch (event.type) {
      case 'request.created':
        handleRequestCreated(event.data);
        break;
      case 'request.evaluated':
        handleRequestEvaluated(event.data);
        break;
    }

    res.json({ received: true });
  } catch (err) {
    if (err instanceof WebhookSignatureVerificationError) {
      console.log('Signature verification failed:', err.message);
      res.status(401).send('Invalid signature');
    } else {
      console.log('Webhook error:', err);
      res.status(400).send('Webhook error');
    }
  }
});

function handleRequestCreated(data: any) {
  console.log('Processing new request:', data);
}

function handleRequestEvaluated(data: any) {
  console.log('Processing evaluated request:', data);
}
```

## API Reference

### `permutapp.constructEvent(payload, signature, secret, tolerance?)`

Constructs and validates a webhook event.

**Parameters:**

- `payload` (string|Buffer): Raw body from webhook request
- `signature` (string): Value from `x-webhook-signature` header
- `secret` (string): Your webhook endpoint secret
- `tolerance` (number, optional): Timestamp tolerance in seconds (default: 300)

**Returns:** `WebhookEvent`

**Throws:** `WebhookSignatureVerificationError`, `WebhookTimestampToleranceError`

### `permutapp.verifySignature(payload, signature, secret, tolerance?)`

Verifies webhook signature without constructing event.

**Parameters:** Same as `constructEvent`

**Returns:** `boolean`

## Event Types

- `request.created` - New request created
- `request.evaluated` - Request evaluation completed
- `request.evaluation_failed` - Request evaluation failed
- `user.created` - New user registered
- `user.updated` - User information updated
- `customer.created` - New customer created
- `customer.updated` - Customer information updated
- `customer.deleted` - Customer deleted
- `test.event` - Test event for endpoint verification

## WebhookEvent Interface

```typescript
interface WebhookEvent {
  id: string;
  type: string;
  created: number;
  data: any;
  livemode: boolean;
}
```

## Error Handling

```typescript
import {
  WebhookSignatureVerificationError,
  WebhookTimestampToleranceError,
} from '@inforbitstudio/permutapp';

try {
  const event = permutapp.constructEvent(payload, signature, secret);
} catch (err) {
  if (err instanceof WebhookSignatureVerificationError) {
    console.log('Invalid signature or payload');
  } else if (err instanceof WebhookTimestampToleranceError) {
    console.log('Timestamp too old - possible replay attack');
  }
}
```

### Error Types

#### WebhookSignatureVerificationError

Thrown when signature verification fails. Includes:

- `message`: Error description
- `payload`: Original payload (if available)
- `header`: Original signature header (if available)

#### WebhookTimestampToleranceError

Thrown when webhook timestamp is outside tolerance window. Includes:

- `message`: Error description
- `timestamp`: Request timestamp
- `tolerance`: Configured tolerance in seconds

## Security

This SDK automatically verifies webhook signatures using HMAC SHA-256 to ensure webhooks are sent by PermutApp and haven't been tampered with.

### Best Practices

1. **Use raw request body**: Always use the raw request body for signature verification
2. **Validate timestamps**: Configure appropriate tolerance to prevent replay attacks
3. **Handle errors properly**: Distinguish between signature and timestamp errors
4. **Secure secret storage**: Store webhook secrets securely and rotate them regularly

### Example Security Implementation

```javascript
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const secret = process.env.PERMUTAPP_WEBHOOK_SECRET;

  if (!signature) {
    return res.status(400).send('Missing signature');
  }

  if (!secret) {
    return res.status(500).send('Webhook secret not configured');
  }

  try {
    const event = permutapp.constructEvent(req.body, signature, secret, 300);

    switch (event.type) {
      case 'request.created':
        // Handle new request
        break;
      case 'request.evaluated':
        // Handle evaluated request
        break;
      case 'user.created':
        // Handle new user
        break;
      default:
        console.log('Unhandled event type:', event.type);
    }

    res.json({ received: true });
  } catch (err) {
    console.error('Webhook verification failed:', err);
    res.status(400).send('Invalid webhook');
  }
});
```

## Common Use Cases

### CRM Integration

```javascript
app.post('/webhook', (req, res) => {
  try {
    const event = permutapp.constructEvent(req.body, signature, secret);

    switch (event.type) {
      case 'request.created':
        // Create lead in CRM
        await createCRMLead({
          email: event.data.user.email,
          name: event.data.user.name,
          car: event.data.vehicle.brand + ' ' + event.data.vehicle.model
        });
        break;

      case 'request.evaluated':
        // Update opportunity in CRM
        await updateCRMOpportunity({
          requestId: event.data.request.id,
          value: event.data.evaluation.estimatedValue,
          status: 'evaluated'
        });
        break;
    }

    res.json({received: true});
  } catch (err) {
    res.status(400).send('Error processing webhook');
  }
});
```

### Email Notifications

```javascript
switch (event.type) {
  case 'request.evaluated':
    await sendEmail({
      to: event.data.user.email,
      subject: 'Your car evaluation is ready',
      template: 'evaluation-complete',
      data: {
        customerName: event.data.user.name,
        carDetails: event.data.vehicle,
        evaluation: event.data.evaluation,
      },
    });
    break;
}
```

## Development

```bash
# Install dependencies
npm install

# Build
npm run build

# Test
npm test

# Lint
npm run lint

# Format
npm run format
```

## Changelog

### v1.0.0

- Initial release with webhook signature verification
- Support for all PermutApp event types
- TypeScript support with full type definitions
- Comprehensive error handling

## License

MIT License - see LICENSE file for details.