@hostwebhook/node 1.6.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +276 -276
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/llms.txt +167 -167
- package/package.json +43 -43
package/README.md
CHANGED
|
@@ -1,276 +1,276 @@
|
|
|
1
|
-
# @hostwebhook/node
|
|
2
|
-
|
|
3
|
-
> **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.
|
|
4
|
-
|
|
5
|
-
Official Node.js SDK for [HostWebhook](https://www.hostwebhook.com) — verify webhook signatures and send webhooks through the platform.
|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
```sh
|
|
10
|
-
npm install @hostwebhook/node
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Requires Node.js 18+.
|
|
14
|
-
|
|
15
|
-
## Verify Webhook Signatures
|
|
16
|
-
|
|
17
|
-
HostWebhook signs every delivery with HMAC-SHA256. Use these helpers to verify authenticity and prevent replay attacks.
|
|
18
|
-
|
|
19
|
-
### Express
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
import express from 'express';
|
|
23
|
-
import { expressMiddleware } from '@hostwebhook/node';
|
|
24
|
-
|
|
25
|
-
const app = express();
|
|
26
|
-
|
|
27
|
-
app.post('/webhooks',
|
|
28
|
-
express.raw({ type: 'application/json' }),
|
|
29
|
-
expressMiddleware(process.env.SIGNING_SECRET!),
|
|
30
|
-
(req, res) => {
|
|
31
|
-
const payload = JSON.parse(req.body.toString());
|
|
32
|
-
// process payload...
|
|
33
|
-
res.json({ received: true });
|
|
34
|
-
},
|
|
35
|
-
);
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
> **Important:** Use `express.raw()` so `req.body` is a Buffer, not parsed JSON.
|
|
39
|
-
|
|
40
|
-
### NestJS
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
import { Controller, Post, UseGuards, Req } from '@nestjs/common';
|
|
44
|
-
import { createNestGuard } from '@hostwebhook/node';
|
|
45
|
-
|
|
46
|
-
const WebhookGuard = createNestGuard(process.env.SIGNING_SECRET!);
|
|
47
|
-
|
|
48
|
-
@Controller('webhooks')
|
|
49
|
-
export class WebhooksController {
|
|
50
|
-
@Post()
|
|
51
|
-
@UseGuards(WebhookGuard)
|
|
52
|
-
handle(@Req() req: Request) {
|
|
53
|
-
return { received: true };
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Fastify
|
|
59
|
-
|
|
60
|
-
```ts
|
|
61
|
-
import Fastify from 'fastify';
|
|
62
|
-
import { fastifyHook } from '@hostwebhook/node';
|
|
63
|
-
|
|
64
|
-
const app = Fastify();
|
|
65
|
-
app.addContentTypeParser('application/json', { parseAs: 'buffer' }, (req, body, done) => done(null, body));
|
|
66
|
-
|
|
67
|
-
app.post('/webhooks', {
|
|
68
|
-
preHandler: fastifyHook(process.env.SIGNING_SECRET!),
|
|
69
|
-
}, (req, reply) => {
|
|
70
|
-
reply.send({ received: true });
|
|
71
|
-
});
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Manual Verification
|
|
75
|
-
|
|
76
|
-
```ts
|
|
77
|
-
import { verify, VerificationError } from '@hostwebhook/node';
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
verify(rawBody, request.headers, secret);
|
|
81
|
-
// signature valid
|
|
82
|
-
} catch (err) {
|
|
83
|
-
if (err instanceof VerificationError) {
|
|
84
|
-
// invalid — reject the request
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Options: `verify(payload, headers, secret, { maxAgeSec: 300 })` — default 300s (5 min). Set `0` to disable replay protection.
|
|
90
|
-
|
|
91
|
-
## Send Webhooks
|
|
92
|
-
|
|
93
|
-
Send webhook payloads through HostWebhook for reliable delivery with retries and monitoring.
|
|
94
|
-
|
|
95
|
-
### Fire & forget (default)
|
|
96
|
-
|
|
97
|
-
```ts
|
|
98
|
-
import { HostWebhook } from '@hostwebhook/node';
|
|
99
|
-
|
|
100
|
-
const hw = new HostWebhook({ token: 'your_ingress_token' });
|
|
101
|
-
|
|
102
|
-
// Returns immediately with the event ID — you don't know what happened in the pipeline
|
|
103
|
-
const { eventId } = await hw.send({ event: 'order.created', orderId: '123' });
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Wait for pipeline result (blocking)
|
|
107
|
-
|
|
108
|
-
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. **This blocks until the pipeline finishes** (~120s max).
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
const result = await hw.send(
|
|
112
|
-
{ event: 'order.created', orderId: '123' },
|
|
113
|
-
{ waitForResult: true },
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
console.log(result.syncStatus); // 200 = delivered, 422 = validation failed, 502 = failed, 408 = timeout
|
|
117
|
-
console.log(result.steps); // array of pipeline steps with nodeType, status, durationMs
|
|
118
|
-
console.log(result.response); // response body from target URL
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### Non-blocking result (recommended for UIs)
|
|
122
|
-
|
|
123
|
-
Use `onResult` to get the pipeline result in a background callback. `send()` returns immediately so your UI never blocks. Perfect for forms and user-facing apps.
|
|
124
|
-
|
|
125
|
-
```ts
|
|
126
|
-
const { eventId } = await hw.send(
|
|
127
|
-
{ event: 'order.created', orderId: '123' },
|
|
128
|
-
{
|
|
129
|
-
onResult: (result) => {
|
|
130
|
-
// Fires when the pipeline completes (via SSE in background)
|
|
131
|
-
if (result.awaitingApproval) {
|
|
132
|
-
showToast('Awaiting approval from ' + result.approvalNodeName);
|
|
133
|
-
} else if (result.syncStatus === 200) {
|
|
134
|
-
showToast('Delivered successfully');
|
|
135
|
-
} else {
|
|
136
|
-
showToast('Pipeline failed: ' + result.error);
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
onStep: (step) => {
|
|
140
|
-
// Optional: fires for each pipeline node as it executes
|
|
141
|
-
updateProgress(step.nodeName, step.status);
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
);
|
|
145
|
-
// UI shows "Sent!" immediately here — never blocks
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
> **Important:** `onResult` requires the endpoint to be in **None** response mode (async). In Sync mode, the server holds the HTTP connection open until the pipeline completes, which blocks `send()` regardless of callbacks. See [Choosing the right mode](#choosing-the-right-mode) below.
|
|
149
|
-
|
|
150
|
-
### Deduplication detection
|
|
151
|
-
|
|
152
|
-
When an event is deduplicated, the response includes `deduplicated: true`:
|
|
153
|
-
|
|
154
|
-
```ts
|
|
155
|
-
const result = await hw.send({ event: 'order.created', orderId: '123' });
|
|
156
|
-
|
|
157
|
-
if (result.deduplicated) {
|
|
158
|
-
console.log('Duplicate! Original event:', result.originalEventId);
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### Batch
|
|
163
|
-
|
|
164
|
-
```ts
|
|
165
|
-
const results = await hw.sendBatch([
|
|
166
|
-
{ event: 'user.signup', userId: '1' },
|
|
167
|
-
{ event: 'user.signup', userId: '2' },
|
|
168
|
-
]);
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Standalone Function
|
|
172
|
-
|
|
173
|
-
```ts
|
|
174
|
-
import { send } from '@hostwebhook/node';
|
|
175
|
-
|
|
176
|
-
const { eventId } = await send(
|
|
177
|
-
{ token: 'your_ingress_token' },
|
|
178
|
-
{ event: 'test', data: 'hello' },
|
|
179
|
-
);
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## Choosing the right mode
|
|
183
|
-
|
|
184
|
-
Your endpoint has a **Response Mode** setting (None or Sync) that affects how `send()` behaves:
|
|
185
|
-
|
|
186
|
-
| Scenario | Endpoint mode | SDK option | Behavior |
|
|
187
|
-
|----------|--------------|------------|----------|
|
|
188
|
-
| **Forms / UIs** | None | `onResult` | `send()` returns instantly. Pipeline result arrives in background callback. **UI never blocks.** |
|
|
189
|
-
| **Backend scripts** | None | `waitForResult` | `send()` blocks until pipeline completes via SSE (~120s max). |
|
|
190
|
-
| **API proxy / Stripe** | Sync | *(none)* | `send()` blocks until pipeline completes via HTTP (~120s max). Result is inline in the response. |
|
|
191
|
-
| **Testing (Postman)** | Sync | *(none)* | Single request, response includes full pipeline result. |
|
|
192
|
-
|
|
193
|
-
> **Warning: Never use Sync mode for user-facing forms or UIs.** Sync mode holds the HTTP connection open on the server, which blocks `send()` and freezes the UI until the pipeline completes. If the pipeline has approval nodes, merge nodes, or long delays, the SDK will timeout. Use **None** mode with `onResult` instead — the user sees "Sent!" immediately and the pipeline result arrives in the background.
|
|
194
|
-
|
|
195
|
-
### When to use Sync mode
|
|
196
|
-
|
|
197
|
-
- The caller is a **server or script**, not a human looking at a UI
|
|
198
|
-
- The caller **needs the pipeline result in the same HTTP request** (e.g., Stripe expects a specific response body)
|
|
199
|
-
- The pipeline is **fast and linear** (filter -> transform -> action) with no human-gated nodes (approval, merge)
|
|
200
|
-
|
|
201
|
-
### When to use None mode + onResult
|
|
202
|
-
|
|
203
|
-
- The caller is a **form, UI, or frontend app**
|
|
204
|
-
- You want **instant feedback** ("Sent!") without waiting for the pipeline
|
|
205
|
-
- The pipeline has **approval nodes, merge nodes, or delays**
|
|
206
|
-
- You want **real-time progress** via `onStep` callbacks
|
|
207
|
-
|
|
208
|
-
## Configuration
|
|
209
|
-
|
|
210
|
-
| Option | Type | Default | Description |
|
|
211
|
-
|--------|------|---------|-------------|
|
|
212
|
-
| `token` | `string` | *required* | Endpoint ingress token |
|
|
213
|
-
| `baseUrl` | `string` | `https://api.hostwebhook.com` | API base URL |
|
|
214
|
-
| `signingSecret` | `string` | — | Signs outgoing requests with HMAC-SHA256 |
|
|
215
|
-
| `signature` | `StaticSignature` | — | Static secret signature (alternative to HMAC) |
|
|
216
|
-
| `defaultHeaders` | `Record<string, string>` | — | Headers added to every request |
|
|
217
|
-
| `timeoutMs` | `number` | `10000` | Request timeout in ms |
|
|
218
|
-
|
|
219
|
-
### SendOptions
|
|
220
|
-
|
|
221
|
-
| Option | Type | Default | Description |
|
|
222
|
-
|--------|------|---------|-------------|
|
|
223
|
-
| `headers` | `Record<string, string>` | — | Per-request headers |
|
|
224
|
-
| `contentType` | `string` | `application/json` | Override content type |
|
|
225
|
-
| `rawBody` | `string \| Buffer` | — | Send raw body instead of JSON |
|
|
226
|
-
| `waitForResult` | `boolean` | `false` | Wait for pipeline result via SSE stream (blocking) |
|
|
227
|
-
| `onResult` | `(result) => void` | — | Non-blocking pipeline result callback via SSE |
|
|
228
|
-
| `onStep` | `(step) => void` | — | Real-time pipeline step callback (with `onResult` or `waitForResult`) |
|
|
229
|
-
|
|
230
|
-
## Error Handling
|
|
231
|
-
|
|
232
|
-
```ts
|
|
233
|
-
import { SendError, VerificationError } from '@hostwebhook/node';
|
|
234
|
-
|
|
235
|
-
// Send errors
|
|
236
|
-
try {
|
|
237
|
-
await hw.send(payload);
|
|
238
|
-
} catch (err) {
|
|
239
|
-
if (err instanceof SendError) {
|
|
240
|
-
console.log(err.statusCode, err.responseBody);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Verification errors
|
|
245
|
-
try {
|
|
246
|
-
verify(body, headers, secret);
|
|
247
|
-
} catch (err) {
|
|
248
|
-
if (err instanceof VerificationError) {
|
|
249
|
-
console.log(err.message);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
## Signature Format
|
|
255
|
-
|
|
256
|
-
```
|
|
257
|
-
X-HostWebhook-Signature: t=<timestamp_ms>,v1=<hmac_hex>
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
HMAC computed as: `HMAC-SHA256(secret, "${timestamp}.${rawBody}")`
|
|
261
|
-
|
|
262
|
-
## Support
|
|
263
|
-
|
|
264
|
-
Found a bug or have a feature request? [Report an issue](https://www.hostwebhook.com/#issues) on our website.
|
|
265
|
-
|
|
266
|
-
## Privacy
|
|
267
|
-
|
|
268
|
-
Your webhook data is yours. HostWebhook is built with privacy as a core principle:
|
|
269
|
-
|
|
270
|
-
- **Your payloads stay between you and your endpoints** — we never sell, share, or use your data for training.
|
|
271
|
-
- **Encryption in transit** — all traffic between the SDK, the platform, and your endpoints is sent over TLS.
|
|
272
|
-
- **Minimal data retention** — event payloads are stored only for delivery and debugging, and are automatically purged based on your plan's retention window.
|
|
273
|
-
|
|
274
|
-
## License
|
|
275
|
-
|
|
276
|
-
MIT
|
|
1
|
+
# @hostwebhook/node
|
|
2
|
+
|
|
3
|
+
> **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.
|
|
4
|
+
|
|
5
|
+
Official Node.js SDK for [HostWebhook](https://www.hostwebhook.com) — verify webhook signatures and send webhooks through the platform.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @hostwebhook/node
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Node.js 18+.
|
|
14
|
+
|
|
15
|
+
## Verify Webhook Signatures
|
|
16
|
+
|
|
17
|
+
HostWebhook signs every delivery with HMAC-SHA256. Use these helpers to verify authenticity and prevent replay attacks.
|
|
18
|
+
|
|
19
|
+
### Express
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import express from 'express';
|
|
23
|
+
import { expressMiddleware } from '@hostwebhook/node';
|
|
24
|
+
|
|
25
|
+
const app = express();
|
|
26
|
+
|
|
27
|
+
app.post('/webhooks',
|
|
28
|
+
express.raw({ type: 'application/json' }),
|
|
29
|
+
expressMiddleware(process.env.SIGNING_SECRET!),
|
|
30
|
+
(req, res) => {
|
|
31
|
+
const payload = JSON.parse(req.body.toString());
|
|
32
|
+
// process payload...
|
|
33
|
+
res.json({ received: true });
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> **Important:** Use `express.raw()` so `req.body` is a Buffer, not parsed JSON.
|
|
39
|
+
|
|
40
|
+
### NestJS
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { Controller, Post, UseGuards, Req } from '@nestjs/common';
|
|
44
|
+
import { createNestGuard } from '@hostwebhook/node';
|
|
45
|
+
|
|
46
|
+
const WebhookGuard = createNestGuard(process.env.SIGNING_SECRET!);
|
|
47
|
+
|
|
48
|
+
@Controller('webhooks')
|
|
49
|
+
export class WebhooksController {
|
|
50
|
+
@Post()
|
|
51
|
+
@UseGuards(WebhookGuard)
|
|
52
|
+
handle(@Req() req: Request) {
|
|
53
|
+
return { received: true };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Fastify
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import Fastify from 'fastify';
|
|
62
|
+
import { fastifyHook } from '@hostwebhook/node';
|
|
63
|
+
|
|
64
|
+
const app = Fastify();
|
|
65
|
+
app.addContentTypeParser('application/json', { parseAs: 'buffer' }, (req, body, done) => done(null, body));
|
|
66
|
+
|
|
67
|
+
app.post('/webhooks', {
|
|
68
|
+
preHandler: fastifyHook(process.env.SIGNING_SECRET!),
|
|
69
|
+
}, (req, reply) => {
|
|
70
|
+
reply.send({ received: true });
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Manual Verification
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { verify, VerificationError } from '@hostwebhook/node';
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
verify(rawBody, request.headers, secret);
|
|
81
|
+
// signature valid
|
|
82
|
+
} catch (err) {
|
|
83
|
+
if (err instanceof VerificationError) {
|
|
84
|
+
// invalid — reject the request
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Options: `verify(payload, headers, secret, { maxAgeSec: 300 })` — default 300s (5 min). Set `0` to disable replay protection.
|
|
90
|
+
|
|
91
|
+
## Send Webhooks
|
|
92
|
+
|
|
93
|
+
Send webhook payloads through HostWebhook for reliable delivery with retries and monitoring.
|
|
94
|
+
|
|
95
|
+
### Fire & forget (default)
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { HostWebhook } from '@hostwebhook/node';
|
|
99
|
+
|
|
100
|
+
const hw = new HostWebhook({ token: 'your_ingress_token' });
|
|
101
|
+
|
|
102
|
+
// Returns immediately with the event ID — you don't know what happened in the pipeline
|
|
103
|
+
const { eventId } = await hw.send({ event: 'order.created', orderId: '123' });
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Wait for pipeline result (blocking)
|
|
107
|
+
|
|
108
|
+
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. **This blocks until the pipeline finishes** (~120s max).
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const result = await hw.send(
|
|
112
|
+
{ event: 'order.created', orderId: '123' },
|
|
113
|
+
{ waitForResult: true },
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
console.log(result.syncStatus); // 200 = delivered, 422 = validation failed, 502 = failed, 408 = timeout
|
|
117
|
+
console.log(result.steps); // array of pipeline steps with nodeType, status, durationMs
|
|
118
|
+
console.log(result.response); // response body from target URL
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Non-blocking result (recommended for UIs)
|
|
122
|
+
|
|
123
|
+
Use `onResult` to get the pipeline result in a background callback. `send()` returns immediately so your UI never blocks. Perfect for forms and user-facing apps.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const { eventId } = await hw.send(
|
|
127
|
+
{ event: 'order.created', orderId: '123' },
|
|
128
|
+
{
|
|
129
|
+
onResult: (result) => {
|
|
130
|
+
// Fires when the pipeline completes (via SSE in background)
|
|
131
|
+
if (result.awaitingApproval) {
|
|
132
|
+
showToast('Awaiting approval from ' + result.approvalNodeName);
|
|
133
|
+
} else if (result.syncStatus === 200) {
|
|
134
|
+
showToast('Delivered successfully');
|
|
135
|
+
} else {
|
|
136
|
+
showToast('Pipeline failed: ' + result.error);
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
onStep: (step) => {
|
|
140
|
+
// Optional: fires for each pipeline node as it executes
|
|
141
|
+
updateProgress(step.nodeName, step.status);
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
// UI shows "Sent!" immediately here — never blocks
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
> **Important:** `onResult` requires the endpoint to be in **None** response mode (async). In Sync mode, the server holds the HTTP connection open until the pipeline completes, which blocks `send()` regardless of callbacks. See [Choosing the right mode](#choosing-the-right-mode) below.
|
|
149
|
+
|
|
150
|
+
### Deduplication detection
|
|
151
|
+
|
|
152
|
+
When an event is deduplicated, the response includes `deduplicated: true`:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
const result = await hw.send({ event: 'order.created', orderId: '123' });
|
|
156
|
+
|
|
157
|
+
if (result.deduplicated) {
|
|
158
|
+
console.log('Duplicate! Original event:', result.originalEventId);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Batch
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
const results = await hw.sendBatch([
|
|
166
|
+
{ event: 'user.signup', userId: '1' },
|
|
167
|
+
{ event: 'user.signup', userId: '2' },
|
|
168
|
+
]);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Standalone Function
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { send } from '@hostwebhook/node';
|
|
175
|
+
|
|
176
|
+
const { eventId } = await send(
|
|
177
|
+
{ token: 'your_ingress_token' },
|
|
178
|
+
{ event: 'test', data: 'hello' },
|
|
179
|
+
);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Choosing the right mode
|
|
183
|
+
|
|
184
|
+
Your endpoint has a **Response Mode** setting (None or Sync) that affects how `send()` behaves:
|
|
185
|
+
|
|
186
|
+
| Scenario | Endpoint mode | SDK option | Behavior |
|
|
187
|
+
|----------|--------------|------------|----------|
|
|
188
|
+
| **Forms / UIs** | None | `onResult` | `send()` returns instantly. Pipeline result arrives in background callback. **UI never blocks.** |
|
|
189
|
+
| **Backend scripts** | None | `waitForResult` | `send()` blocks until pipeline completes via SSE (~120s max). |
|
|
190
|
+
| **API proxy / Stripe** | Sync | *(none)* | `send()` blocks until pipeline completes via HTTP (~120s max). Result is inline in the response. |
|
|
191
|
+
| **Testing (Postman)** | Sync | *(none)* | Single request, response includes full pipeline result. |
|
|
192
|
+
|
|
193
|
+
> **Warning: Never use Sync mode for user-facing forms or UIs.** Sync mode holds the HTTP connection open on the server, which blocks `send()` and freezes the UI until the pipeline completes. If the pipeline has approval nodes, merge nodes, or long delays, the SDK will timeout. Use **None** mode with `onResult` instead — the user sees "Sent!" immediately and the pipeline result arrives in the background.
|
|
194
|
+
|
|
195
|
+
### When to use Sync mode
|
|
196
|
+
|
|
197
|
+
- The caller is a **server or script**, not a human looking at a UI
|
|
198
|
+
- The caller **needs the pipeline result in the same HTTP request** (e.g., Stripe expects a specific response body)
|
|
199
|
+
- The pipeline is **fast and linear** (filter -> transform -> action) with no human-gated nodes (approval, merge)
|
|
200
|
+
|
|
201
|
+
### When to use None mode + onResult
|
|
202
|
+
|
|
203
|
+
- The caller is a **form, UI, or frontend app**
|
|
204
|
+
- You want **instant feedback** ("Sent!") without waiting for the pipeline
|
|
205
|
+
- The pipeline has **approval nodes, merge nodes, or delays**
|
|
206
|
+
- You want **real-time progress** via `onStep` callbacks
|
|
207
|
+
|
|
208
|
+
## Configuration
|
|
209
|
+
|
|
210
|
+
| Option | Type | Default | Description |
|
|
211
|
+
|--------|------|---------|-------------|
|
|
212
|
+
| `token` | `string` | *required* | Endpoint ingress token |
|
|
213
|
+
| `baseUrl` | `string` | `https://api.hostwebhook.com` | API base URL |
|
|
214
|
+
| `signingSecret` | `string` | — | Signs outgoing requests with HMAC-SHA256 |
|
|
215
|
+
| `signature` | `StaticSignature` | — | Static secret signature (alternative to HMAC) |
|
|
216
|
+
| `defaultHeaders` | `Record<string, string>` | — | Headers added to every request |
|
|
217
|
+
| `timeoutMs` | `number` | `10000` | Request timeout in ms |
|
|
218
|
+
|
|
219
|
+
### SendOptions
|
|
220
|
+
|
|
221
|
+
| Option | Type | Default | Description |
|
|
222
|
+
|--------|------|---------|-------------|
|
|
223
|
+
| `headers` | `Record<string, string>` | — | Per-request headers |
|
|
224
|
+
| `contentType` | `string` | `application/json` | Override content type |
|
|
225
|
+
| `rawBody` | `string \| Buffer` | — | Send raw body instead of JSON |
|
|
226
|
+
| `waitForResult` | `boolean` | `false` | Wait for pipeline result via SSE stream (blocking) |
|
|
227
|
+
| `onResult` | `(result) => void` | — | Non-blocking pipeline result callback via SSE |
|
|
228
|
+
| `onStep` | `(step) => void` | — | Real-time pipeline step callback (with `onResult` or `waitForResult`) |
|
|
229
|
+
|
|
230
|
+
## Error Handling
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { SendError, VerificationError } from '@hostwebhook/node';
|
|
234
|
+
|
|
235
|
+
// Send errors
|
|
236
|
+
try {
|
|
237
|
+
await hw.send(payload);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
if (err instanceof SendError) {
|
|
240
|
+
console.log(err.statusCode, err.responseBody);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Verification errors
|
|
245
|
+
try {
|
|
246
|
+
verify(body, headers, secret);
|
|
247
|
+
} catch (err) {
|
|
248
|
+
if (err instanceof VerificationError) {
|
|
249
|
+
console.log(err.message);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Signature Format
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
X-HostWebhook-Signature: t=<timestamp_ms>,v1=<hmac_hex>
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
HMAC computed as: `HMAC-SHA256(secret, "${timestamp}.${rawBody}")`
|
|
261
|
+
|
|
262
|
+
## Support
|
|
263
|
+
|
|
264
|
+
Found a bug or have a feature request? [Report an issue](https://www.hostwebhook.com/#issues) on our website.
|
|
265
|
+
|
|
266
|
+
## Privacy
|
|
267
|
+
|
|
268
|
+
Your webhook data is yours. HostWebhook is built with privacy as a core principle:
|
|
269
|
+
|
|
270
|
+
- **Your payloads stay between you and your endpoints** — we never sell, share, or use your data for training.
|
|
271
|
+
- **Encryption in transit** — all traffic between the SDK, the platform, and your endpoints is sent over TLS.
|
|
272
|
+
- **Minimal data retention** — event payloads are stored only for delivery and debugging, and are automatically purged based on your plan's retention window.
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -192,7 +192,7 @@ interface HostWebhookConfig {
|
|
|
192
192
|
apiKey?: string;
|
|
193
193
|
/**
|
|
194
194
|
* Default ingress token. When set, `send(payload)` uses this token.
|
|
195
|
-
* For multi-
|
|
195
|
+
* For multi-webhook usage, pass the token per-call: `send(token, payload)`.
|
|
196
196
|
*/
|
|
197
197
|
token?: string;
|
|
198
198
|
/** Base URL of the HostWebhook API. Default: `https://api.hostwebhook.com` */
|
|
@@ -200,7 +200,7 @@ interface HostWebhookConfig {
|
|
|
200
200
|
/**
|
|
201
201
|
* HMAC-SHA256 signing secret. When set, every request is signed with the
|
|
202
202
|
* HostWebhook signature format (`X-HostWebhook-Signature: t=<ts>,v1=<hmac>`).
|
|
203
|
-
* Pair with `incomingSignatureType: 'hostwebhook'` on the
|
|
203
|
+
* Pair with `incomingSignatureType: 'hostwebhook'` on the webhook.
|
|
204
204
|
* @deprecated Prefer `apiKey` for authentication.
|
|
205
205
|
*/
|
|
206
206
|
signingSecret?: string;
|
|
@@ -262,7 +262,7 @@ interface SendOptions {
|
|
|
262
262
|
* it does NOT wait for the approval to be resolved (that can take hours/days)
|
|
263
263
|
* - Delay nodes — if the delay exceeds the 125s timeout, the stream times out
|
|
264
264
|
*
|
|
265
|
-
* For approval workflows, use `responseMode: 'sync'` on the
|
|
265
|
+
* For approval workflows, use `responseMode: 'sync'` on the webhook instead —
|
|
266
266
|
* it returns the approval status inline without blocking the SSE stream.
|
|
267
267
|
*/
|
|
268
268
|
waitForResult?: boolean;
|
|
@@ -324,13 +324,13 @@ declare class SendError extends Error {
|
|
|
324
324
|
/**
|
|
325
325
|
* HostWebhook client for sending webhooks through the platform.
|
|
326
326
|
*
|
|
327
|
-
* @example Single-
|
|
327
|
+
* @example Single-webhook
|
|
328
328
|
* ```ts
|
|
329
329
|
* const hw = new HostWebhook({ apiKey: 'hwk_...', token: 'ingress_token' });
|
|
330
330
|
* await hw.send({ event: 'order.created' });
|
|
331
331
|
* ```
|
|
332
332
|
*
|
|
333
|
-
* @example Multi-
|
|
333
|
+
* @example Multi-webhook
|
|
334
334
|
* ```ts
|
|
335
335
|
* const hw = new HostWebhook({ apiKey: 'hwk_...' });
|
|
336
336
|
* await hw.send('token_a', { event: 'order.created' });
|
package/dist/index.d.ts
CHANGED
|
@@ -192,7 +192,7 @@ interface HostWebhookConfig {
|
|
|
192
192
|
apiKey?: string;
|
|
193
193
|
/**
|
|
194
194
|
* Default ingress token. When set, `send(payload)` uses this token.
|
|
195
|
-
* For multi-
|
|
195
|
+
* For multi-webhook usage, pass the token per-call: `send(token, payload)`.
|
|
196
196
|
*/
|
|
197
197
|
token?: string;
|
|
198
198
|
/** Base URL of the HostWebhook API. Default: `https://api.hostwebhook.com` */
|
|
@@ -200,7 +200,7 @@ interface HostWebhookConfig {
|
|
|
200
200
|
/**
|
|
201
201
|
* HMAC-SHA256 signing secret. When set, every request is signed with the
|
|
202
202
|
* HostWebhook signature format (`X-HostWebhook-Signature: t=<ts>,v1=<hmac>`).
|
|
203
|
-
* Pair with `incomingSignatureType: 'hostwebhook'` on the
|
|
203
|
+
* Pair with `incomingSignatureType: 'hostwebhook'` on the webhook.
|
|
204
204
|
* @deprecated Prefer `apiKey` for authentication.
|
|
205
205
|
*/
|
|
206
206
|
signingSecret?: string;
|
|
@@ -262,7 +262,7 @@ interface SendOptions {
|
|
|
262
262
|
* it does NOT wait for the approval to be resolved (that can take hours/days)
|
|
263
263
|
* - Delay nodes — if the delay exceeds the 125s timeout, the stream times out
|
|
264
264
|
*
|
|
265
|
-
* For approval workflows, use `responseMode: 'sync'` on the
|
|
265
|
+
* For approval workflows, use `responseMode: 'sync'` on the webhook instead —
|
|
266
266
|
* it returns the approval status inline without blocking the SSE stream.
|
|
267
267
|
*/
|
|
268
268
|
waitForResult?: boolean;
|
|
@@ -324,13 +324,13 @@ declare class SendError extends Error {
|
|
|
324
324
|
/**
|
|
325
325
|
* HostWebhook client for sending webhooks through the platform.
|
|
326
326
|
*
|
|
327
|
-
* @example Single-
|
|
327
|
+
* @example Single-webhook
|
|
328
328
|
* ```ts
|
|
329
329
|
* const hw = new HostWebhook({ apiKey: 'hwk_...', token: 'ingress_token' });
|
|
330
330
|
* await hw.send({ event: 'order.created' });
|
|
331
331
|
* ```
|
|
332
332
|
*
|
|
333
|
-
* @example Multi-
|
|
333
|
+
* @example Multi-webhook
|
|
334
334
|
* ```ts
|
|
335
335
|
* const hw = new HostWebhook({ apiKey: 'hwk_...' });
|
|
336
336
|
* await hw.send('token_a', { event: 'order.created' });
|
package/dist/index.js
CHANGED
|
@@ -410,7 +410,7 @@ doSend_fn = async function(token, payload, options) {
|
|
|
410
410
|
}
|
|
411
411
|
const headers = {
|
|
412
412
|
"Content-Type": options?.contentType ?? "application/json",
|
|
413
|
-
"User-Agent": `HostWebhook-SDK/${"1.
|
|
413
|
+
"User-Agent": `HostWebhook-SDK/${"1.6.1"}`,
|
|
414
414
|
"X-HostWebhook-Sent-At": (/* @__PURE__ */ new Date()).toISOString(),
|
|
415
415
|
...tz ? { "X-HostWebhook-Timezone": tz } : {},
|
|
416
416
|
...__privateGet(this, _defaultHeaders),
|
package/dist/index.mjs
CHANGED
|
@@ -376,7 +376,7 @@ doSend_fn = async function(token, payload, options) {
|
|
|
376
376
|
}
|
|
377
377
|
const headers = {
|
|
378
378
|
"Content-Type": options?.contentType ?? "application/json",
|
|
379
|
-
"User-Agent": `HostWebhook-SDK/${"1.
|
|
379
|
+
"User-Agent": `HostWebhook-SDK/${"1.6.1"}`,
|
|
380
380
|
"X-HostWebhook-Sent-At": (/* @__PURE__ */ new Date()).toISOString(),
|
|
381
381
|
...tz ? { "X-HostWebhook-Timezone": tz } : {},
|
|
382
382
|
...__privateGet(this, _defaultHeaders),
|
package/llms.txt
CHANGED
|
@@ -1,167 +1,167 @@
|
|
|
1
|
-
# @hostwebhook/node
|
|
2
|
-
|
|
3
|
-
> Official HostWebhook Node.js SDK — verify webhook signatures and send webhooks through the platform.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
npm install @hostwebhook/node
|
|
8
|
-
|
|
9
|
-
## Verify Signatures (receiving webhooks)
|
|
10
|
-
|
|
11
|
-
HostWebhook signs every delivery with HMAC-SHA256. Use these helpers to verify authenticity.
|
|
12
|
-
|
|
13
|
-
### Express
|
|
14
|
-
|
|
15
|
-
```ts
|
|
16
|
-
import express from 'express';
|
|
17
|
-
import { expressMiddleware } from '@hostwebhook/node';
|
|
18
|
-
|
|
19
|
-
const app = express();
|
|
20
|
-
app.post('/webhooks',
|
|
21
|
-
express.raw({ type: 'application/json' }),
|
|
22
|
-
expressMiddleware(process.env.SIGNING_SECRET!),
|
|
23
|
-
(req, res) => {
|
|
24
|
-
const payload = JSON.parse(req.body.toString());
|
|
25
|
-
// process payload...
|
|
26
|
-
res.json({ received: true });
|
|
27
|
-
},
|
|
28
|
-
);
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
IMPORTANT: Use `express.raw()` so `req.body` is a Buffer, not parsed JSON.
|
|
32
|
-
|
|
33
|
-
### NestJS
|
|
34
|
-
|
|
35
|
-
```ts
|
|
36
|
-
import { Controller, Post, UseGuards, Req } from '@nestjs/common';
|
|
37
|
-
import { createNestGuard } from '@hostwebhook/node';
|
|
38
|
-
|
|
39
|
-
const WebhookGuard = createNestGuard(process.env.SIGNING_SECRET!);
|
|
40
|
-
|
|
41
|
-
@Controller('webhooks')
|
|
42
|
-
export class WebhooksController {
|
|
43
|
-
@Post()
|
|
44
|
-
@UseGuards(WebhookGuard)
|
|
45
|
-
handle(@Req() req: Request) {
|
|
46
|
-
return { received: true };
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Fastify
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
import Fastify from 'fastify';
|
|
55
|
-
import { fastifyHook } from '@hostwebhook/node';
|
|
56
|
-
|
|
57
|
-
const app = Fastify();
|
|
58
|
-
app.addContentTypeParser('application/json', { parseAs: 'buffer' }, (req, body, done) => done(null, body));
|
|
59
|
-
|
|
60
|
-
app.post('/webhooks', {
|
|
61
|
-
preHandler: fastifyHook(process.env.SIGNING_SECRET!),
|
|
62
|
-
}, (req, reply) => {
|
|
63
|
-
reply.send({ received: true });
|
|
64
|
-
});
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### Manual verification
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
import { verify, VerificationError } from '@hostwebhook/node';
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
verify(rawBody, request.headers, secret);
|
|
74
|
-
// signature is valid
|
|
75
|
-
} catch (err) {
|
|
76
|
-
if (err instanceof VerificationError) {
|
|
77
|
-
// invalid signature — reject
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
verify(payload, headers, secret, options?) — throws VerificationError if invalid.
|
|
83
|
-
VerifyOptions: { maxAgeSec?: number } — default 300 (5 min). Set 0 to disable replay protection.
|
|
84
|
-
|
|
85
|
-
## Send Webhooks (sending through HostWebhook)
|
|
86
|
-
|
|
87
|
-
### Class-based client
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
import { HostWebhook } from '@hostwebhook/node';
|
|
91
|
-
|
|
92
|
-
const hw = new HostWebhook({ token: 'your_ingress_token' });
|
|
93
|
-
|
|
94
|
-
// Send a single event
|
|
95
|
-
const { eventId } = await hw.send({ event: 'order.created', orderId: '123' });
|
|
96
|
-
|
|
97
|
-
// Send multiple events in parallel
|
|
98
|
-
const results = await hw.sendBatch([
|
|
99
|
-
{ event: 'user.signup', userId: '1' },
|
|
100
|
-
{ event: 'user.signup', userId: '2' },
|
|
101
|
-
]);
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Standalone function (no class needed)
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
import { send } from '@hostwebhook/node';
|
|
108
|
-
|
|
109
|
-
const { eventId } = await send(
|
|
110
|
-
{ token: 'your_ingress_token' },
|
|
111
|
-
{ event: 'test', data: 'hello' },
|
|
112
|
-
);
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Configuration
|
|
116
|
-
|
|
117
|
-
HostWebhookConfig: {
|
|
118
|
-
token: string (required — your endpoint's ingress token),
|
|
119
|
-
baseUrl?: string (default: https://api.hostwebhook.com),
|
|
120
|
-
signingSecret?: string (optional — signs outgoing requests with HMAC-SHA256),
|
|
121
|
-
defaultHeaders?: Record<string, string>,
|
|
122
|
-
timeoutMs?: number (default: 10000),
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
SendOptions: {
|
|
126
|
-
headers?: Record<string, string>,
|
|
127
|
-
contentType?: string (default: application/json),
|
|
128
|
-
rawBody?: string | Buffer (skip JSON.stringify, send raw),
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
SendResult: { eventId: string }
|
|
132
|
-
|
|
133
|
-
## Signature Format
|
|
134
|
-
|
|
135
|
-
Header: `X-HostWebhook-Signature: t=<timestamp_ms>,v1=<hmac_hex>`
|
|
136
|
-
HMAC computed as: `HMAC-SHA256(secret, "${timestamp}.${rawBody}")`
|
|
137
|
-
|
|
138
|
-
## Error Handling
|
|
139
|
-
|
|
140
|
-
```ts
|
|
141
|
-
import { SendError, VerificationError } from '@hostwebhook/node';
|
|
142
|
-
|
|
143
|
-
// SendError — thrown when API returns non-2xx
|
|
144
|
-
try {
|
|
145
|
-
await hw.send(payload);
|
|
146
|
-
} catch (err) {
|
|
147
|
-
if (err instanceof SendError) {
|
|
148
|
-
console.log(err.statusCode, err.responseBody);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// VerificationError — thrown when signature is invalid
|
|
153
|
-
try {
|
|
154
|
-
verify(body, headers, secret);
|
|
155
|
-
} catch (err) {
|
|
156
|
-
if (err instanceof VerificationError) {
|
|
157
|
-
console.log(err.message); // e.g. "Signature mismatch"
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
## Exports
|
|
163
|
-
|
|
164
|
-
Functions: verify, expressMiddleware, createNestGuard, fastifyHook, send
|
|
165
|
-
Classes: HostWebhook, SendError, VerificationError
|
|
166
|
-
Types: VerifyOptions, HostWebhookConfig, SendOptions, SendResult, WebhookHeaders
|
|
167
|
-
Helpers: parseHeaders (extract webhook metadata from headers)
|
|
1
|
+
# @hostwebhook/node
|
|
2
|
+
|
|
3
|
+
> Official HostWebhook Node.js SDK — verify webhook signatures and send webhooks through the platform.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
npm install @hostwebhook/node
|
|
8
|
+
|
|
9
|
+
## Verify Signatures (receiving webhooks)
|
|
10
|
+
|
|
11
|
+
HostWebhook signs every delivery with HMAC-SHA256. Use these helpers to verify authenticity.
|
|
12
|
+
|
|
13
|
+
### Express
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import express from 'express';
|
|
17
|
+
import { expressMiddleware } from '@hostwebhook/node';
|
|
18
|
+
|
|
19
|
+
const app = express();
|
|
20
|
+
app.post('/webhooks',
|
|
21
|
+
express.raw({ type: 'application/json' }),
|
|
22
|
+
expressMiddleware(process.env.SIGNING_SECRET!),
|
|
23
|
+
(req, res) => {
|
|
24
|
+
const payload = JSON.parse(req.body.toString());
|
|
25
|
+
// process payload...
|
|
26
|
+
res.json({ received: true });
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
IMPORTANT: Use `express.raw()` so `req.body` is a Buffer, not parsed JSON.
|
|
32
|
+
|
|
33
|
+
### NestJS
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { Controller, Post, UseGuards, Req } from '@nestjs/common';
|
|
37
|
+
import { createNestGuard } from '@hostwebhook/node';
|
|
38
|
+
|
|
39
|
+
const WebhookGuard = createNestGuard(process.env.SIGNING_SECRET!);
|
|
40
|
+
|
|
41
|
+
@Controller('webhooks')
|
|
42
|
+
export class WebhooksController {
|
|
43
|
+
@Post()
|
|
44
|
+
@UseGuards(WebhookGuard)
|
|
45
|
+
handle(@Req() req: Request) {
|
|
46
|
+
return { received: true };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Fastify
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import Fastify from 'fastify';
|
|
55
|
+
import { fastifyHook } from '@hostwebhook/node';
|
|
56
|
+
|
|
57
|
+
const app = Fastify();
|
|
58
|
+
app.addContentTypeParser('application/json', { parseAs: 'buffer' }, (req, body, done) => done(null, body));
|
|
59
|
+
|
|
60
|
+
app.post('/webhooks', {
|
|
61
|
+
preHandler: fastifyHook(process.env.SIGNING_SECRET!),
|
|
62
|
+
}, (req, reply) => {
|
|
63
|
+
reply.send({ received: true });
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Manual verification
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { verify, VerificationError } from '@hostwebhook/node';
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
verify(rawBody, request.headers, secret);
|
|
74
|
+
// signature is valid
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (err instanceof VerificationError) {
|
|
77
|
+
// invalid signature — reject
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
verify(payload, headers, secret, options?) — throws VerificationError if invalid.
|
|
83
|
+
VerifyOptions: { maxAgeSec?: number } — default 300 (5 min). Set 0 to disable replay protection.
|
|
84
|
+
|
|
85
|
+
## Send Webhooks (sending through HostWebhook)
|
|
86
|
+
|
|
87
|
+
### Class-based client
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { HostWebhook } from '@hostwebhook/node';
|
|
91
|
+
|
|
92
|
+
const hw = new HostWebhook({ token: 'your_ingress_token' });
|
|
93
|
+
|
|
94
|
+
// Send a single event
|
|
95
|
+
const { eventId } = await hw.send({ event: 'order.created', orderId: '123' });
|
|
96
|
+
|
|
97
|
+
// Send multiple events in parallel
|
|
98
|
+
const results = await hw.sendBatch([
|
|
99
|
+
{ event: 'user.signup', userId: '1' },
|
|
100
|
+
{ event: 'user.signup', userId: '2' },
|
|
101
|
+
]);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Standalone function (no class needed)
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { send } from '@hostwebhook/node';
|
|
108
|
+
|
|
109
|
+
const { eventId } = await send(
|
|
110
|
+
{ token: 'your_ingress_token' },
|
|
111
|
+
{ event: 'test', data: 'hello' },
|
|
112
|
+
);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Configuration
|
|
116
|
+
|
|
117
|
+
HostWebhookConfig: {
|
|
118
|
+
token: string (required — your endpoint's ingress token),
|
|
119
|
+
baseUrl?: string (default: https://api.hostwebhook.com),
|
|
120
|
+
signingSecret?: string (optional — signs outgoing requests with HMAC-SHA256),
|
|
121
|
+
defaultHeaders?: Record<string, string>,
|
|
122
|
+
timeoutMs?: number (default: 10000),
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
SendOptions: {
|
|
126
|
+
headers?: Record<string, string>,
|
|
127
|
+
contentType?: string (default: application/json),
|
|
128
|
+
rawBody?: string | Buffer (skip JSON.stringify, send raw),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
SendResult: { eventId: string }
|
|
132
|
+
|
|
133
|
+
## Signature Format
|
|
134
|
+
|
|
135
|
+
Header: `X-HostWebhook-Signature: t=<timestamp_ms>,v1=<hmac_hex>`
|
|
136
|
+
HMAC computed as: `HMAC-SHA256(secret, "${timestamp}.${rawBody}")`
|
|
137
|
+
|
|
138
|
+
## Error Handling
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { SendError, VerificationError } from '@hostwebhook/node';
|
|
142
|
+
|
|
143
|
+
// SendError — thrown when API returns non-2xx
|
|
144
|
+
try {
|
|
145
|
+
await hw.send(payload);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
if (err instanceof SendError) {
|
|
148
|
+
console.log(err.statusCode, err.responseBody);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// VerificationError — thrown when signature is invalid
|
|
153
|
+
try {
|
|
154
|
+
verify(body, headers, secret);
|
|
155
|
+
} catch (err) {
|
|
156
|
+
if (err instanceof VerificationError) {
|
|
157
|
+
console.log(err.message); // e.g. "Signature mismatch"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Exports
|
|
163
|
+
|
|
164
|
+
Functions: verify, expressMiddleware, createNestGuard, fastifyHook, send
|
|
165
|
+
Classes: HostWebhook, SendError, VerificationError
|
|
166
|
+
Types: VerifyOptions, HostWebhookConfig, SendOptions, SendResult, WebhookHeaders
|
|
167
|
+
Helpers: parseHeaders (extract webhook metadata from headers)
|
package/package.json
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@hostwebhook/node",
|
|
3
|
-
"version": "1.6.
|
|
4
|
-
"description": "Official HostWebhook SDK — verify signatures, parse headers, and send webhooks",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.mjs",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.mjs",
|
|
12
|
-
"require": "./dist/index.js"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist",
|
|
17
|
-
"README.md",
|
|
18
|
-
"llms.txt"
|
|
19
|
-
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"build": "tsup",
|
|
22
|
-
"test": "node --test __tests__/sdk.test.mjs",
|
|
23
|
-
"prepublishOnly": "npm run build"
|
|
24
|
-
},
|
|
25
|
-
"keywords": [
|
|
26
|
-
"hostwebhook",
|
|
27
|
-
"webhook",
|
|
28
|
-
"signature",
|
|
29
|
-
"verify",
|
|
30
|
-
"send",
|
|
31
|
-
"hmac",
|
|
32
|
-
"retry"
|
|
33
|
-
],
|
|
34
|
-
"license": "MIT",
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"@types/node": "^25.3.3",
|
|
37
|
-
"tsup": "^8.0.0",
|
|
38
|
-
"typescript": "^5.3.0"
|
|
39
|
-
},
|
|
40
|
-
"engines": {
|
|
41
|
-
"node": ">=18"
|
|
42
|
-
}
|
|
43
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@hostwebhook/node",
|
|
3
|
+
"version": "1.6.1",
|
|
4
|
+
"description": "Official HostWebhook SDK — verify signatures, parse headers, and send webhooks",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"llms.txt"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"test": "node --test __tests__/sdk.test.mjs",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"hostwebhook",
|
|
27
|
+
"webhook",
|
|
28
|
+
"signature",
|
|
29
|
+
"verify",
|
|
30
|
+
"send",
|
|
31
|
+
"hmac",
|
|
32
|
+
"retry"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^25.3.3",
|
|
37
|
+
"tsup": "^8.0.0",
|
|
38
|
+
"typescript": "^5.3.0"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
}
|
|
43
|
+
}
|