npm package: @hookbridge/sdk
Description: Official HookBridge SDK for Node.js and TypeScript
Author: HookBridge
Latest version: 1.5.0

# HookBridge Node.js SDK

Official HookBridge SDK for Node.js and TypeScript. Send webhooks with guaranteed delivery, automatic retries, and full observability.

## Installation

```bash
npm install @hookbridge/sdk
```

## Quick Start

```typescript
import { HookBridge } from '@hookbridge/sdk';

const hookbridge = new HookBridge({
  apiKey: 'hb_live_xxxxxxxxxxxxxxxxxxxx'
});

// First, create an endpoint for your customer
const endpoint = await hookbridge.createEndpoint({
  url: 'https://customer.app/webhooks',
  description: 'Customer production webhook'
});
console.log('Endpoint ID:', endpoint.id);
console.log('Signing Secret:', endpoint.signingSecret); // Store this securely!

// Send a webhook to that endpoint
const result = await hookbridge.send({
  endpointId: endpoint.id,
  payload: {
    event: 'order.created',
    orderId: 'ord_12345',
    amount: 99.99
  }
});

console.log('Message ID:', result.messageId);
```

## Features

- **Guaranteed Delivery**: Webhooks are stored durably before acknowledgment
- **Automatic Retries**: Intelligent retry with exponential backoff
- **Idempotency**: Prevent duplicate webhook sends
- **Full Observability**: Query logs, metrics, and message status
- **Type Safety**: Full TypeScript support with detailed types

## Usage

### Create an Endpoint

Endpoints must be created before sending webhooks. The signing secret is only returned once.

```typescript
const endpoint = await hookbridge.createEndpoint({
  url: 'https://customer.app/webhooks',
  description: 'Main production webhook',
  hmacEnabled: true,  // Enable HMAC signing (default: true)
  rateLimitRps: 10,   // Optional rate limiting
  burst: 20
});

// Store the signing secret securely - it's only shown once!
console.log(endpoint.signingSecret);
```

### Send a Webhook

```typescript
const result = await hookbridge.send({
  endpointId: 'ep_550e8400e29b41d4a716446655440000',
  payload: { event: 'user.created', userId: 'usr_123' },
  headers: { 'X-Tenant-Id': 'tenant_abc' },
  idempotencyKey: 'user-123-created'
});
```

### Check Message Status

```typescript
const message = await hookbridge.getMessage(result.messageId);
console.log(message.status); // 'queued', 'succeeded', 'pending_retry', 'failed_permanent'
```

### Query Delivery Logs

```typescript
const logs = await hookbridge.getLogs({
  status: 'failed_permanent',
  startTime: new Date('2025-01-01'),
  limit: 100
});

for (const msg of logs.messages) {
  console.log(msg.messageId, msg.lastError);
}
```

### Get Metrics

```typescript
const metrics = await hookbridge.getMetrics('24h');
console.log(`Success rate: ${(metrics.successRate * 100).toFixed(1)}%`);
console.log(`Average latency: ${metrics.avgLatencyMs}ms`);
```

### Replay Failed Messages

```typescript
// Replay a specific message
await hookbridge.replay(messageId);

// Or replay from the Dead Letter Queue
const dlq = await hookbridge.getDLQMessages();
for (const msg of dlq.messages) {
  await hookbridge.replayFromDLQ(msg.messageId);
}
```

### Manage Retries

```typescript
// Cancel a pending retry (moves to DLQ)
await hookbridge.cancelRetry(messageId);

// Trigger immediate retry for a pending message
await hookbridge.retryNow(messageId);
```

### API Key Management

```typescript
// List API keys
const keys = await hookbridge.listAPIKeys();

// Create a new API key
const newKey = await hookbridge.createAPIKey({
  label: 'Production backend',
  mode: 'live'
});
console.log('Save this key:', newKey.key); // Only shown once!

// Delete an API key
await hookbridge.deleteAPIKey('key_abc123');
```

### Endpoint Management

```typescript
// List all endpoints
const endpoints = await hookbridge.listEndpoints({ limit: 50 });
for (const ep of endpoints.endpoints) {
  console.log(ep.id, ep.url);
}

// Get endpoint details
const endpoint = await hookbridge.getEndpoint('ep_550e8400e29b41d4a716446655440000');

// Update an endpoint
await hookbridge.updateEndpoint('ep_550e8400e29b41d4a716446655440000', {
  url: 'https://new-customer.app/webhooks',
  rateLimitRps: 20
});

// Rotate signing secret (if compromised)
const result = await hookbridge.rotateEndpointSecret('ep_550e8400e29b41d4a716446655440000');
console.log('New secret:', result.signingSecret); // Store securely!

// Delete an endpoint
await hookbridge.deleteEndpoint('ep_550e8400e29b41d4a716446655440000');
```

### Inbound Webhooks

```typescript
const inbound = await hookbridge.createInboundEndpoint({
  url: 'https://myapp.com/webhooks/inbound',
  name: 'Stripe inbound',
  description: 'Receives Stripe events through HookBridge',
  verifyStaticToken: true,
  tokenHeaderName: 'X-Webhook-Token',
  tokenValue: 'my-shared-secret',
  signingEnabled: true,
  idempotencyHeaderNames: ['X-Idempotency-Key'],
  ingestResponseCode: 202,
});

console.log('Inbound endpoint ID:', inbound.id);
console.log('Send webhooks here:', inbound.ingestUrl); // Save this
console.log('Secret token:', inbound.secretToken);     // Only shown once

const inboundDetails = await hookbridge.getInboundEndpoint(inbound.id);
await hookbridge.pauseInboundEndpoint(inbound.id);
await hookbridge.resumeInboundEndpoint(inbound.id);

await hookbridge.updateInboundEndpoint(inbound.id, {
  verifyHmac: true,
  hmacHeaderName: 'X-Signature',
  hmacSecret: 'whsec_inbound_secret',
});
```

### Inbound Observability

```typescript
const inboundEndpoints = await hookbridge.listInboundEndpoints({ limit: 50 });

const logs = await hookbridge.getInboundLogs({
  inboundEndpointId: '01935abc-def0-7123-4567-890abcdef012',
  limit: 50,
});

for (const entry of logs.entries) {
  console.log(entry.messageId, entry.status, entry.totalDeliveryMs);
}

const inboundMetrics = await hookbridge.getInboundMetrics({
  window: '24h',
  inboundEndpointId: '01935abc-def0-7123-4567-890abcdef012',
});

const inboundTimeseries = await hookbridge.getInboundTimeseriesMetrics({
  window: '24h',
  inboundEndpointId: '01935abc-def0-7123-4567-890abcdef012',
});

const rejections = await hookbridge.listInboundRejections({
  inboundEndpointId: '01935abc-def0-7123-4567-890abcdef012',
  limit: 25,
});

for (const rejection of rejections.entries) {
  console.log(rejection.reasonCode, rejection.reasonDetail);
}
```

## Configuration

```typescript
const hookbridge = new HookBridge({
  // Required: Your API key
  apiKey: 'hb_live_xxxxxxxxxxxxxxxxxxxx',

  // Optional: Custom base URL for management API (default: https://api.hookbridge.io)
  baseUrl: 'https://api.hookbridge.io',

  // Optional: Custom URL for webhook send API (default: https://send.hookbridge.io)
  sendUrl: 'https://send.hookbridge.io',

  // Optional: Request timeout in ms (default: 30000)
  timeout: 30000,

  // Optional: Number of retries for failed requests (default: 3)
  retries: 3
});
```

### Environment Variables

You can also configure the SDK using environment variables:

- `HOOKBRIDGE_BASE_URL` - Base URL for management API operations
- `HOOKBRIDGE_SEND_URL` - Base URL for webhook send operations

These are useful for testing against staging environments.

## Error Handling

```typescript
import {
  HookBridge,
  AuthenticationError,
  ValidationError,
  RateLimitError,
  NotFoundError,
  IdempotencyError
} from '@hookbridge/sdk';

try {
  await hookbridge.send({ ... });
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Invalid API key');
  } else if (error instanceof ValidationError) {
    console.error('Invalid request:', error.message);
  } else if (error instanceof RateLimitError) {
    console.error(`Rate limited. Retry after ${error.retryAfter}s`);
  } else if (error instanceof IdempotencyError) {
    console.error('Duplicate request with different payload');
  } else {
    throw error;
  }
}
```

## Webhook Delivery

When HookBridge delivers your webhook, it includes these headers:

- `X-Webhook-Signature`: HMAC-SHA256 signature for verification
- `X-Webhook-Id`: Message ID for tracking
- `X-Webhook-Timestamp`: Unix timestamp of the send request
- Any custom headers you specified

### Retry Behavior

- **Fast retries** (for 429 responses): 30s, 60s, 120s, 240s, 300s
- **Slow retries** (for other errors): 30m, 2h, 6h, 12h, 24h, 48h, 72h, 96h
- Maximum 8 total attempts before moving to the Dead Letter Queue

## Requirements

- Node.js 18.0.0 or later

## License

MIT