npm package: @hostwebhook/node
Description: Official HostWebhook SDK — verify signatures, parse headers, and send webhooks
Latest version: 1.2.0

# @hostwebhook/node

> **Early Access** — This SDK is in early access. The API may change between versions. We'd love your feedback — [report an issue](https://www.hostwebhook.com/#issues) if you run into anything.

Official Node.js SDK for [HostWebhook](https://www.hostwebhook.com) — verify webhook signatures and send webhooks through the platform.

## Install

```sh
npm install @hostwebhook/node
```

Requires Node.js 18+.

## Verify Webhook Signatures

HostWebhook signs every delivery with HMAC-SHA256. Use these helpers to verify authenticity and prevent replay attacks.

### Express

```ts
import express from 'express';
import { expressMiddleware } from '@hostwebhook/node';

const app = express();

app.post('/webhooks',
  express.raw({ type: 'application/json' }),
  expressMiddleware(process.env.SIGNING_SECRET!),
  (req, res) => {
    const payload = JSON.parse(req.body.toString());
    // process payload...
    res.json({ received: true });
  },
);
```

> **Important:** Use `express.raw()` so `req.body` is a Buffer, not parsed JSON.

### NestJS

```ts
import { Controller, Post, UseGuards, Req } from '@nestjs/common';
import { createNestGuard } from '@hostwebhook/node';

const WebhookGuard = createNestGuard(process.env.SIGNING_SECRET!);

@Controller('webhooks')
export class WebhooksController {
  @Post()
  @UseGuards(WebhookGuard)
  handle(@Req() req: Request) {
    return { received: true };
  }
}
```

### Fastify

```ts
import Fastify from 'fastify';
import { fastifyHook } from '@hostwebhook/node';

const app = Fastify();
app.addContentTypeParser('application/json', { parseAs: 'buffer' }, (req, body, done) => done(null, body));

app.post('/webhooks', {
  preHandler: fastifyHook(process.env.SIGNING_SECRET!),
}, (req, reply) => {
  reply.send({ received: true });
});
```

### Manual Verification

```ts
import { verify, VerificationError } from '@hostwebhook/node';

try {
  verify(rawBody, request.headers, secret);
  // signature valid
} catch (err) {
  if (err instanceof VerificationError) {
    // invalid — reject the request
  }
}
```

Options: `verify(payload, headers, secret, { maxAgeSec: 300 })` — default 300s (5 min). Set `0` to disable replay protection.

## Send Webhooks

Send webhook payloads through HostWebhook for reliable delivery with retries and monitoring.

### Class-based

```ts
import { HostWebhook } from '@hostwebhook/node';

const hw = new HostWebhook({ token: 'your_ingress_token' });

const { eventId } = await hw.send({ event: 'order.created', orderId: '123' });
```

### Wait for pipeline result (SSE streaming)

Use `waitForResult` to wait for the full pipeline to complete. The SDK opens an SSE stream behind the scenes and returns the result with all pipeline steps.

```ts
const result = await hw.send(
  { event: 'order.created', orderId: '123' },
  { waitForResult: true },
);

console.log(result.syncStatus);  // 200 = delivered, 422 = validation failed, 502 = failed, 408 = timeout
console.log(result.steps);       // array of pipeline steps with nodeType, status, durationMs
console.log(result.response);    // response body from target URL
```

Works with any endpoint regardless of its response mode. Max wait: ~120 seconds.

### Deduplication detection

When an event is deduplicated, the response includes `deduplicated: true`:

```ts
const result = await hw.send({ event: 'order.created', orderId: '123' });

if (result.deduplicated) {
  console.log('Duplicate! Original event:', result.originalEventId);
}
```

### Batch

```ts
const results = await hw.sendBatch([
  { event: 'user.signup', userId: '1' },
  { event: 'user.signup', userId: '2' },
]);
```

### Standalone Function

```ts
import { send } from '@hostwebhook/node';

const { eventId } = await send(
  { token: 'your_ingress_token' },
  { event: 'test', data: 'hello' },
);
```

## Configuration

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `token` | `string` | *required* | Endpoint ingress token |
| `baseUrl` | `string` | `https://api.hostwebhook.com` | API base URL |
| `signingSecret` | `string` | — | Signs outgoing requests with HMAC-SHA256 |
| `signature` | `StaticSignature` | — | Static secret signature (alternative to HMAC) |
| `defaultHeaders` | `Record<string, string>` | — | Headers added to every request |
| `timeoutMs` | `number` | `10000` | Request timeout in ms |

### SendOptions

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `headers` | `Record<string, string>` | — | Per-request headers |
| `contentType` | `string` | `application/json` | Override content type |
| `rawBody` | `string \| Buffer` | — | Send raw body instead of JSON |
| `waitForResult` | `boolean` | `false` | Wait for pipeline result via SSE stream |

## Error Handling

```ts
import { SendError, VerificationError } from '@hostwebhook/node';

// Send errors
try {
  await hw.send(payload);
} catch (err) {
  if (err instanceof SendError) {
    console.log(err.statusCode, err.responseBody);
  }
}

// Verification errors
try {
  verify(body, headers, secret);
} catch (err) {
  if (err instanceof VerificationError) {
    console.log(err.message);
  }
}
```

## Signature Format

```
X-HostWebhook-Signature: t=<timestamp_ms>,v1=<hmac_hex>
```

HMAC computed as: `HMAC-SHA256(secret, "${timestamp}.${rawBody}")`

## Support

Found a bug or have a feature request? [Report an issue](https://www.hostwebhook.com/#issues) on our website.

## Privacy

Your webhook data is yours. HostWebhook is built with privacy as a core principle:

- **Your payloads stay between you and your endpoints** — we never sell, share, or use your data for training.
- **Encryption in transit** — all traffic between the SDK, the platform, and your endpoints is sent over TLS.
- **Minimal data retention** — event payloads are stored only for delivery and debugging, and are automatically purged based on your plan's retention window.

## License

MIT