@hostwebhook/node 1.3.0 → 1.4.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/dist/index.d.mts +411 -0
- package/dist/index.d.ts +411 -0
- package/dist/index.js +289 -96
- package/dist/index.mjs +288 -96
- package/package.json +1 -1
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when signature verification fails.
|
|
3
|
+
*/
|
|
4
|
+
declare class VerificationError extends Error {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
interface VerifyOptions {
|
|
8
|
+
/** Max allowed age of the signature timestamp in seconds. Default: 300 (5 min). Set 0 to disable. */
|
|
9
|
+
maxAgeSec?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Verify a HostWebhook signature.
|
|
13
|
+
*
|
|
14
|
+
* Matches the format produced by HostWebhook's delivery processor:
|
|
15
|
+
* `X-HostWebhook-Signature: t=<timestamp_ms>,v1=<hmac_hex>`
|
|
16
|
+
*
|
|
17
|
+
* The HMAC is computed as: HMAC-SHA256(secret, `${timestamp}.${rawBody}`)
|
|
18
|
+
*
|
|
19
|
+
* @param payload - The raw request body (string or Buffer)
|
|
20
|
+
* @param headers - The request headers object
|
|
21
|
+
* @param secret - Your signing secret (e.g. `whsec_...`)
|
|
22
|
+
* @param options - Optional configuration
|
|
23
|
+
* @throws {VerificationError} If the signature is missing, malformed, expired, or invalid
|
|
24
|
+
*/
|
|
25
|
+
declare function verify(payload: string | Buffer, headers: Record<string, string | string[] | undefined>, secret: string, options?: VerifyOptions): void;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Typed representation of HostWebhook delivery headers.
|
|
29
|
+
*/
|
|
30
|
+
interface WebhookHeaders {
|
|
31
|
+
/** Unique event ID (`X-HostWebhook-Event-Id`) */
|
|
32
|
+
eventId: string;
|
|
33
|
+
/** Delivery attempt number (`X-HostWebhook-Attempt`) */
|
|
34
|
+
attempt: number;
|
|
35
|
+
/** Signature timestamp in milliseconds */
|
|
36
|
+
timestamp: number;
|
|
37
|
+
/** Raw HMAC-SHA256 hex signature */
|
|
38
|
+
signature: string;
|
|
39
|
+
/** Chain step number, if this is a chained delivery */
|
|
40
|
+
chainStep?: number;
|
|
41
|
+
/** Original chain payload (base64-decoded JSON) */
|
|
42
|
+
chainOriginal?: unknown;
|
|
43
|
+
/** Previous chain step response (base64-decoded JSON) */
|
|
44
|
+
chainPrevious?: unknown;
|
|
45
|
+
/** Chain response metadata (base64-decoded JSON) */
|
|
46
|
+
chainResponse?: {
|
|
47
|
+
status: number;
|
|
48
|
+
body: unknown;
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
};
|
|
51
|
+
/** Whether the chain response was truncated */
|
|
52
|
+
chainResponseTruncated?: boolean;
|
|
53
|
+
/** Original size of the chain response before truncation */
|
|
54
|
+
chainResponseOriginalSize?: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Parse all `X-HostWebhook-*` headers into a typed object.
|
|
58
|
+
*
|
|
59
|
+
* @param headers - The raw request headers
|
|
60
|
+
* @returns Parsed webhook headers
|
|
61
|
+
*/
|
|
62
|
+
declare function parseHeaders(headers: Record<string, string | string[] | undefined>): WebhookHeaders;
|
|
63
|
+
|
|
64
|
+
type NextFn = (err?: unknown) => void;
|
|
65
|
+
interface ExpressRequest {
|
|
66
|
+
body: string | Buffer;
|
|
67
|
+
headers: Record<string, string | string[] | undefined>;
|
|
68
|
+
}
|
|
69
|
+
interface ExpressResponse {
|
|
70
|
+
status(code: number): ExpressResponse;
|
|
71
|
+
json(body: unknown): void;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Express middleware that verifies HostWebhook signatures.
|
|
75
|
+
*
|
|
76
|
+
* IMPORTANT: You must use `express.raw({ type: 'application/json' })` before
|
|
77
|
+
* this middleware so that `req.body` is a Buffer (not parsed JSON).
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* import express from 'express';
|
|
82
|
+
* import { expressMiddleware } from '@hostwebhook/node';
|
|
83
|
+
*
|
|
84
|
+
* const app = express();
|
|
85
|
+
* app.post('/webhooks',
|
|
86
|
+
* express.raw({ type: 'application/json' }),
|
|
87
|
+
* expressMiddleware(process.env.SIGNING_SECRET!),
|
|
88
|
+
* (req, res) => {
|
|
89
|
+
* const payload = JSON.parse(req.body.toString());
|
|
90
|
+
* res.status(200).json({ received: true });
|
|
91
|
+
* },
|
|
92
|
+
* );
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
declare function expressMiddleware(secret: string, options?: VerifyOptions): (req: ExpressRequest, res: ExpressResponse, next: NextFn) => void;
|
|
96
|
+
/**
|
|
97
|
+
* NestJS guard factory. Returns a class that can be used with @UseGuards().
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* import { Controller, Post, UseGuards, Req } from '@nestjs/common';
|
|
102
|
+
* import { createNestGuard } from '@hostwebhook/node';
|
|
103
|
+
*
|
|
104
|
+
* const WebhookGuard = createNestGuard(process.env.SIGNING_SECRET!);
|
|
105
|
+
*
|
|
106
|
+
* @Controller('webhooks')
|
|
107
|
+
* export class WebhooksController {
|
|
108
|
+
* @Post()
|
|
109
|
+
* @UseGuards(WebhookGuard)
|
|
110
|
+
* handle(@Req() req: Request) {
|
|
111
|
+
* return { received: true };
|
|
112
|
+
* }
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
declare function createNestGuard(secret: string, options?: VerifyOptions): {
|
|
117
|
+
new (): {
|
|
118
|
+
canActivate(context: {
|
|
119
|
+
switchToHttp(): {
|
|
120
|
+
getRequest(): ExpressRequest;
|
|
121
|
+
};
|
|
122
|
+
}): boolean;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Fastify preHandler hook that verifies HostWebhook signatures.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* import Fastify from 'fastify';
|
|
131
|
+
* import { fastifyHook } from '@hostwebhook/node';
|
|
132
|
+
*
|
|
133
|
+
* const app = Fastify({ bodyLimit: 1_048_576 });
|
|
134
|
+
* app.addContentTypeParser('application/json', { parseAs: 'buffer' }, (req, body, done) => done(null, body));
|
|
135
|
+
*
|
|
136
|
+
* app.post('/webhooks', { preHandler: fastifyHook(process.env.SIGNING_SECRET!) }, (req, reply) => {
|
|
137
|
+
* reply.send({ received: true });
|
|
138
|
+
* });
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
declare function fastifyHook(secret: string, options?: VerifyOptions): (req: ExpressRequest, reply: ExpressResponse, done: NextFn) => void;
|
|
142
|
+
|
|
143
|
+
/** Static secret signature — sends the secret as-is in a header (no HMAC). */
|
|
144
|
+
interface StaticSignature {
|
|
145
|
+
type: 'static';
|
|
146
|
+
/** The secret/token value to send. */
|
|
147
|
+
secret: string;
|
|
148
|
+
/**
|
|
149
|
+
* Header name to send the secret in.
|
|
150
|
+
* Default: `Authorization` with `Bearer <secret>` format.
|
|
151
|
+
* @example 'X-My-Custom-Key'
|
|
152
|
+
*/
|
|
153
|
+
header?: string;
|
|
154
|
+
}
|
|
155
|
+
/** Configuration for the HostWebhook client. */
|
|
156
|
+
interface HostWebhookConfig {
|
|
157
|
+
/**
|
|
158
|
+
* API key (`hwk_...`) for authentication. Required for file uploads
|
|
159
|
+
* and recommended for all operations. Must have appropriate scopes.
|
|
160
|
+
*/
|
|
161
|
+
apiKey?: string;
|
|
162
|
+
/**
|
|
163
|
+
* Default ingress token. When set, `send(payload)` uses this token.
|
|
164
|
+
* For multi-endpoint usage, pass the token per-call: `send(token, payload)`.
|
|
165
|
+
*/
|
|
166
|
+
token?: string;
|
|
167
|
+
/** Base URL of the HostWebhook API. Default: `https://api.hostwebhook.com` */
|
|
168
|
+
baseUrl?: string;
|
|
169
|
+
/**
|
|
170
|
+
* HMAC-SHA256 signing secret. When set, every request is signed with the
|
|
171
|
+
* HostWebhook signature format (`X-HostWebhook-Signature: t=<ts>,v1=<hmac>`).
|
|
172
|
+
* Pair with `incomingSignatureType: 'hostwebhook'` on the endpoint.
|
|
173
|
+
* @deprecated Prefer `apiKey` for authentication.
|
|
174
|
+
*/
|
|
175
|
+
signingSecret?: string;
|
|
176
|
+
/**
|
|
177
|
+
* Signature configuration for incoming webhook verification.
|
|
178
|
+
* Cannot be used together with `signingSecret`.
|
|
179
|
+
*
|
|
180
|
+
* @example Static with default header (Authorization: Bearer)
|
|
181
|
+
* ```ts
|
|
182
|
+
* { type: 'static', secret: 'my-secret' }
|
|
183
|
+
* ```
|
|
184
|
+
* @example Static with custom header
|
|
185
|
+
* ```ts
|
|
186
|
+
* { type: 'static', secret: 'my-secret', header: 'X-My-Custom-Key' }
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
signature?: StaticSignature;
|
|
190
|
+
/** Custom headers to include in every request. */
|
|
191
|
+
defaultHeaders?: Record<string, string>;
|
|
192
|
+
/** Request timeout in milliseconds. Default: 10000 (10s). */
|
|
193
|
+
timeoutMs?: number;
|
|
194
|
+
}
|
|
195
|
+
/** A single pipeline execution step received via SSE stream. */
|
|
196
|
+
interface PipelineStep {
|
|
197
|
+
eventId?: string;
|
|
198
|
+
nodeType: string;
|
|
199
|
+
nodeId: string;
|
|
200
|
+
nodeName?: string;
|
|
201
|
+
status: 'running' | 'success' | 'error' | 'blocked';
|
|
202
|
+
statusCode?: number;
|
|
203
|
+
durationMs?: number;
|
|
204
|
+
error?: string;
|
|
205
|
+
lastPayload?: Record<string, unknown>;
|
|
206
|
+
}
|
|
207
|
+
/** Options for a single send call. */
|
|
208
|
+
interface SendOptions {
|
|
209
|
+
/** Custom headers for this specific request (merged with defaultHeaders). */
|
|
210
|
+
headers?: Record<string, string>;
|
|
211
|
+
/** Override the Content-Type. Default: `application/json`. */
|
|
212
|
+
contentType?: string;
|
|
213
|
+
/**
|
|
214
|
+
* Send raw string/Buffer body instead of JSON-stringifying a payload object.
|
|
215
|
+
* When set, the `payload` parameter to `send()` is ignored.
|
|
216
|
+
*/
|
|
217
|
+
rawBody?: string | Buffer;
|
|
218
|
+
/**
|
|
219
|
+
* Wait for the full pipeline result via SSE streaming (blocking).
|
|
220
|
+
* The SDK will POST the payload, then open an SSE stream to receive
|
|
221
|
+
* pipeline steps and the final result. Returns when the pipeline completes.
|
|
222
|
+
*
|
|
223
|
+
* **Timeout:** ~125s (slightly above the server's 120s SSE timeout).
|
|
224
|
+
*
|
|
225
|
+
* **When to use:** Short pipelines where you need the result before
|
|
226
|
+
* responding to the user (e.g., validation, quick transforms).
|
|
227
|
+
*
|
|
228
|
+
* **When NOT to use:**
|
|
229
|
+
* - Long-running pipelines (file processing, scraping) — use fire-and-forget instead
|
|
230
|
+
* - Approval nodes — the stream returns `{ awaitingApproval: true }` immediately,
|
|
231
|
+
* it does NOT wait for the approval to be resolved (that can take hours/days)
|
|
232
|
+
* - Delay nodes — if the delay exceeds the 125s timeout, the stream times out
|
|
233
|
+
*
|
|
234
|
+
* For approval workflows, use `responseMode: 'sync'` on the endpoint instead —
|
|
235
|
+
* it returns the approval status inline without blocking the SSE stream.
|
|
236
|
+
*/
|
|
237
|
+
waitForResult?: boolean;
|
|
238
|
+
/**
|
|
239
|
+
* Non-blocking pipeline result callback. `send()` returns immediately with
|
|
240
|
+
* the eventId, and the callback fires in the background when the pipeline
|
|
241
|
+
* completes (via SSE stream).
|
|
242
|
+
*
|
|
243
|
+
* Same timeout limitations as `waitForResult` (125s max).
|
|
244
|
+
* For long pipelines, use WebSocket subscription instead.
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```ts
|
|
248
|
+
* const { eventId } = await hw.send(payload, {
|
|
249
|
+
* onResult: (result) => console.log('Pipeline done:', result.syncStatus),
|
|
250
|
+
* });
|
|
251
|
+
* // UI shows "Sent!" immediately here
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
onResult?: (result: SendResult) => void;
|
|
255
|
+
/**
|
|
256
|
+
* Called for each pipeline step during SSE streaming.
|
|
257
|
+
* Only fires when `onResult` or `waitForResult` is used.
|
|
258
|
+
*/
|
|
259
|
+
onStep?: (step: PipelineStep) => void;
|
|
260
|
+
}
|
|
261
|
+
/** Result returned after a successful send. */
|
|
262
|
+
interface SendResult {
|
|
263
|
+
/** The event ID assigned by HostWebhook. */
|
|
264
|
+
eventId: string;
|
|
265
|
+
/** Short-lived token for SSE stream auth (returned by ingress, used internally by waitForResult). */
|
|
266
|
+
streamToken?: string;
|
|
267
|
+
/** Sync/stream: pipeline result status (200=delivered, 422=validation failed, 502=delivery failed, 408=timeout) */
|
|
268
|
+
syncStatus?: number;
|
|
269
|
+
/** Sync/stream: delivery status ('delivered') */
|
|
270
|
+
deliveryStatus?: string;
|
|
271
|
+
/** Sync/stream: HTTP status code from the target URL */
|
|
272
|
+
statusCode?: number;
|
|
273
|
+
/** Sync/stream: response body from the target URL (truncated to 4KB) */
|
|
274
|
+
response?: string;
|
|
275
|
+
/** Sync/stream: delivery latency in ms */
|
|
276
|
+
latencyMs?: number;
|
|
277
|
+
/** Sync/stream: error message if pipeline/delivery failed */
|
|
278
|
+
error?: string;
|
|
279
|
+
/** Sync/stream: validation error messages (schema validator) */
|
|
280
|
+
message?: string[];
|
|
281
|
+
/** SSE stream: pipeline execution steps (only when waitForResult is used) */
|
|
282
|
+
steps?: PipelineStep[];
|
|
283
|
+
/** Any additional fields from the sync response */
|
|
284
|
+
[key: string]: unknown;
|
|
285
|
+
}
|
|
286
|
+
/** Thrown when the HostWebhook API returns a non-2xx response. */
|
|
287
|
+
declare class SendError extends Error {
|
|
288
|
+
readonly statusCode: number;
|
|
289
|
+
readonly responseBody: string;
|
|
290
|
+
constructor(message: string, statusCode: number, responseBody: string);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* HostWebhook client for sending webhooks through the platform.
|
|
295
|
+
*
|
|
296
|
+
* @example Single-endpoint
|
|
297
|
+
* ```ts
|
|
298
|
+
* const hw = new HostWebhook({ apiKey: 'hwk_...', token: 'ingress_token' });
|
|
299
|
+
* await hw.send({ event: 'order.created' });
|
|
300
|
+
* ```
|
|
301
|
+
*
|
|
302
|
+
* @example Multi-endpoint
|
|
303
|
+
* ```ts
|
|
304
|
+
* const hw = new HostWebhook({ apiKey: 'hwk_...' });
|
|
305
|
+
* await hw.send('token_a', { event: 'order.created' });
|
|
306
|
+
* await hw.send('token_b', { event: 'user.updated' });
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
declare class HostWebhook {
|
|
310
|
+
#private;
|
|
311
|
+
constructor(config: HostWebhookConfig);
|
|
312
|
+
/**
|
|
313
|
+
* Send a webhook payload through HostWebhook.
|
|
314
|
+
*
|
|
315
|
+
* @overload send(payload, options?) — uses default token from constructor
|
|
316
|
+
* @overload send(token, payload, options?) — specify token per call
|
|
317
|
+
*
|
|
318
|
+
* @throws {SendError} If the API returns a non-2xx status
|
|
319
|
+
* @throws {Error} If no token is available
|
|
320
|
+
*/
|
|
321
|
+
send(tokenOrPayload: string | Record<string, unknown>, payloadOrOptions?: Record<string, unknown> | SendOptions, maybeOptions?: SendOptions): Promise<SendResult>;
|
|
322
|
+
/**
|
|
323
|
+
* Convenience: send multiple payloads in parallel.
|
|
324
|
+
*
|
|
325
|
+
* @param payloads - Array of payloads to send
|
|
326
|
+
* @param options - Optional per-request configuration (shared for all)
|
|
327
|
+
* @returns Array of results (in same order as payloads)
|
|
328
|
+
*/
|
|
329
|
+
sendBatch(payloads: Record<string, unknown>[], options?: SendOptions): Promise<SendResult[]>;
|
|
330
|
+
/**
|
|
331
|
+
* Send a webhook with file attachments.
|
|
332
|
+
*
|
|
333
|
+
* Requires `apiKey` with `canvas:write` scope for file operations.
|
|
334
|
+
* Files are uploaded via presigned URLs, then references are injected into the payload.
|
|
335
|
+
*
|
|
336
|
+
* @overload sendWithFiles(payload, files, options?) — uses default token
|
|
337
|
+
* @overload sendWithFiles(token, payload, files, options?) — specify token per call
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```ts
|
|
341
|
+
* const hw = new HostWebhook({ apiKey: 'hwk_...', token: 'ingress_token' });
|
|
342
|
+
* await hw.sendWithFiles(
|
|
343
|
+
* { orderId: '123' },
|
|
344
|
+
* { invoice: { data: pdfBuffer, fileName: 'inv.pdf', mimeType: 'application/pdf' } },
|
|
345
|
+
* );
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
sendWithFiles(tokenOrPayload: string | Record<string, unknown>, payloadOrFiles: Record<string, unknown> | Record<string, {
|
|
349
|
+
data: Buffer | Blob | Uint8Array;
|
|
350
|
+
fileName: string;
|
|
351
|
+
mimeType: string;
|
|
352
|
+
}>, filesOrOptions?: Record<string, {
|
|
353
|
+
data: Buffer | Blob | Uint8Array;
|
|
354
|
+
fileName: string;
|
|
355
|
+
mimeType: string;
|
|
356
|
+
}> | SendOptions, maybeOptions?: SendOptions): Promise<SendResult>;
|
|
357
|
+
/**
|
|
358
|
+
* Send a webhook and stream pipeline steps as an async iterable.
|
|
359
|
+
*
|
|
360
|
+
* Much simpler than `waitForResult` or `onResult` callbacks.
|
|
361
|
+
* Iteration ends when the pipeline completes. Access `stream.result`
|
|
362
|
+
* after the loop for the final pipeline result.
|
|
363
|
+
*
|
|
364
|
+
* @overload stream(payload) — uses default token
|
|
365
|
+
* @overload stream(token, payload) — specify token per call
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ```ts
|
|
369
|
+
* const hw = new HostWebhook({ apiKey: 'hwk_...', token: 'ingress_token' });
|
|
370
|
+
* const stream = await hw.stream({ event: 'order.created' });
|
|
371
|
+
*
|
|
372
|
+
* for await (const step of stream) {
|
|
373
|
+
* console.log(`${step.nodeType}: ${step.status}`);
|
|
374
|
+
* }
|
|
375
|
+
* console.log('Done:', stream.result);
|
|
376
|
+
* ```
|
|
377
|
+
*/
|
|
378
|
+
stream(tokenOrPayload: string | Record<string, unknown>, maybePayload?: Record<string, unknown>): Promise<PipelineStream>;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Async iterable pipeline stream. Yields PipelineStep objects as
|
|
382
|
+
* nodes execute, then completes. Access `.result` after iteration.
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* ```ts
|
|
386
|
+
* const stream = await hw.stream(payload);
|
|
387
|
+
* for await (const step of stream) {
|
|
388
|
+
* console.log(step.nodeType, step.status);
|
|
389
|
+
* }
|
|
390
|
+
* console.log(stream.result); // { syncStatus: 200, ... }
|
|
391
|
+
* ```
|
|
392
|
+
*/
|
|
393
|
+
declare class PipelineStream {
|
|
394
|
+
#private;
|
|
395
|
+
result: SendResult;
|
|
396
|
+
constructor(steps: PipelineStep[], result: SendResult);
|
|
397
|
+
static connect(streamUrl: string, ingressResult: SendResult): Promise<PipelineStream>;
|
|
398
|
+
[Symbol.asyncIterator](): AsyncGenerator<PipelineStep, void, undefined>;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Standalone send function (no class instantiation needed).
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* ```ts
|
|
405
|
+
* import { send } from '@hostwebhook/node';
|
|
406
|
+
* await send({ token: 'your_token' }, { event: 'test' });
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
declare function send(config: HostWebhookConfig, payload: Record<string, unknown>, options?: SendOptions): Promise<SendResult>;
|
|
410
|
+
|
|
411
|
+
export { HostWebhook, type HostWebhookConfig, type PipelineStep, PipelineStream, SendError, type SendOptions, type SendResult, type StaticSignature, VerificationError, type VerifyOptions, type WebhookHeaders, createNestGuard, expressMiddleware, fastifyHook, parseHeaders, send, verify };
|