@hostwebhook/node 1.6.2 → 1.6.4
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/LICENSE +2 -0
- package/README.md +12 -3
- package/dist/index.d.mts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +12 -1
- package/dist/index.mjs +12 -1
- package/llms.txt +1 -1
- package/package.json +2 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Copyright (c) 2026 HostWebhook (hostwebhook.com). All rights reserved.
|
|
2
|
+
This software is proprietary. You may install and use it only to access or integrate with the HostWebhook service, under the HostWebhook Terms of Service (https://www.hostwebhook.com/terms). You may not copy, modify, distribute, sublicense, sell, or create derivative works of it, except as needed for that use. No other rights are granted.
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Requires Node.js 18+.
|
|
|
14
14
|
|
|
15
15
|
## Verify Webhook Signatures
|
|
16
16
|
|
|
17
|
-
HostWebhook signs every delivery with HMAC-SHA256. Use these helpers to verify authenticity and
|
|
17
|
+
HostWebhook signs every delivery with HMAC-SHA256. Use these helpers to verify authenticity and to reject stale signatures (see [What is signed](#what-is-signed)).
|
|
18
18
|
|
|
19
19
|
### Express
|
|
20
20
|
|
|
@@ -86,7 +86,9 @@ try {
|
|
|
86
86
|
}
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
Options: `verify(payload, headers, secret, { maxAgeSec: 300 })` — default 300s (5 min). Set `0` to disable
|
|
89
|
+
Options: `verify(payload, headers, secret, { maxAgeSec: 300 })` — default 300s (5 min). Set `0` to disable the age check.
|
|
90
|
+
|
|
91
|
+
`verify` and the middlewares refuse an empty signing secret (an unset environment variable is the usual cause): with an empty key anyone can compute a valid signature.
|
|
90
92
|
|
|
91
93
|
## Send Webhooks
|
|
92
94
|
|
|
@@ -259,6 +261,13 @@ X-HostWebhook-Signature: t=<timestamp_ms>,v1=<hmac_hex>
|
|
|
259
261
|
|
|
260
262
|
HMAC computed as: `HMAC-SHA256(secret, "${timestamp}.${rawBody}")`
|
|
261
263
|
|
|
264
|
+
### What is signed
|
|
265
|
+
|
|
266
|
+
The signature covers **only the timestamp and the raw body**. In particular:
|
|
267
|
+
|
|
268
|
+
- The other `X-HostWebhook-*` headers — the event id, the attempt number and every `chain-*` header — are **not signed**. Anyone who can reach your endpoint can add or change them, so use them as hints (for example to deduplicate), never as proof.
|
|
269
|
+
- The timestamp limits how **old** a signature may be (`maxAgeSec`). It does not stop the same signed request from being sent again inside that window: to reject duplicates, remember the event ids you already processed.
|
|
270
|
+
|
|
262
271
|
## Support
|
|
263
272
|
|
|
264
273
|
Found a bug or have a feature request? [Report an issue](https://www.hostwebhook.com/#issues) on our website.
|
|
@@ -273,4 +282,4 @@ Your webhook data is yours. HostWebhook is built with privacy as a core principl
|
|
|
273
282
|
|
|
274
283
|
## License
|
|
275
284
|
|
|
276
|
-
|
|
285
|
+
Proprietary. See the `LICENSE` file in this package. You may use it only to access or integrate with the HostWebhook service, under the [HostWebhook Terms of Service](https://www.hostwebhook.com/terms).
|
package/dist/index.d.mts
CHANGED
|
@@ -16,16 +16,28 @@ interface VerifyOptions {
|
|
|
16
16
|
*
|
|
17
17
|
* The HMAC is computed as: HMAC-SHA256(secret, `${timestamp}.${rawBody}`)
|
|
18
18
|
*
|
|
19
|
+
* What is signed: ONLY the timestamp and the raw body. The other
|
|
20
|
+
* `X-HostWebhook-*` headers (event id, attempt, chain-*) are NOT covered by the
|
|
21
|
+
* signature: anyone who can reach your endpoint can add or change them. Do not
|
|
22
|
+
* make security decisions from them. And the timestamp only limits how OLD a
|
|
23
|
+
* signature may be (`maxAgeSec`); it does not stop the same signed request from
|
|
24
|
+
* being replayed inside that window. To reject duplicates, remember the event
|
|
25
|
+
* id of what you already processed.
|
|
26
|
+
*
|
|
19
27
|
* @param payload - The raw request body (string or Buffer)
|
|
20
28
|
* @param headers - The request headers object
|
|
21
29
|
* @param secret - Your signing secret (e.g. `whsec_...`)
|
|
22
30
|
* @param options - Optional configuration
|
|
23
|
-
* @throws {VerificationError} If the signature is missing, malformed, expired, or invalid
|
|
31
|
+
* @throws {VerificationError} If the secret is empty, or the signature is missing, malformed, expired, or invalid
|
|
24
32
|
*/
|
|
25
33
|
declare function verify(payload: string | Buffer, headers: Record<string, string | string[] | undefined>, secret: string, options?: VerifyOptions): void;
|
|
26
34
|
|
|
27
35
|
/**
|
|
28
36
|
* Typed representation of HostWebhook delivery headers.
|
|
37
|
+
*
|
|
38
|
+
* ⚠️ Only `timestamp` and the body are covered by the signature. The event id,
|
|
39
|
+
* the attempt number and every `chain*` field come from headers that are NOT
|
|
40
|
+
* signed: treat them as hints (for example to deduplicate), never as proof.
|
|
29
41
|
*/
|
|
30
42
|
interface WebhookHeaders {
|
|
31
43
|
/** Unique event ID (`X-HostWebhook-Event-Id`) */
|
package/dist/index.d.ts
CHANGED
|
@@ -16,16 +16,28 @@ interface VerifyOptions {
|
|
|
16
16
|
*
|
|
17
17
|
* The HMAC is computed as: HMAC-SHA256(secret, `${timestamp}.${rawBody}`)
|
|
18
18
|
*
|
|
19
|
+
* What is signed: ONLY the timestamp and the raw body. The other
|
|
20
|
+
* `X-HostWebhook-*` headers (event id, attempt, chain-*) are NOT covered by the
|
|
21
|
+
* signature: anyone who can reach your endpoint can add or change them. Do not
|
|
22
|
+
* make security decisions from them. And the timestamp only limits how OLD a
|
|
23
|
+
* signature may be (`maxAgeSec`); it does not stop the same signed request from
|
|
24
|
+
* being replayed inside that window. To reject duplicates, remember the event
|
|
25
|
+
* id of what you already processed.
|
|
26
|
+
*
|
|
19
27
|
* @param payload - The raw request body (string or Buffer)
|
|
20
28
|
* @param headers - The request headers object
|
|
21
29
|
* @param secret - Your signing secret (e.g. `whsec_...`)
|
|
22
30
|
* @param options - Optional configuration
|
|
23
|
-
* @throws {VerificationError} If the signature is missing, malformed, expired, or invalid
|
|
31
|
+
* @throws {VerificationError} If the secret is empty, or the signature is missing, malformed, expired, or invalid
|
|
24
32
|
*/
|
|
25
33
|
declare function verify(payload: string | Buffer, headers: Record<string, string | string[] | undefined>, secret: string, options?: VerifyOptions): void;
|
|
26
34
|
|
|
27
35
|
/**
|
|
28
36
|
* Typed representation of HostWebhook delivery headers.
|
|
37
|
+
*
|
|
38
|
+
* ⚠️ Only `timestamp` and the body are covered by the signature. The event id,
|
|
39
|
+
* the attempt number and every `chain*` field come from headers that are NOT
|
|
40
|
+
* signed: treat them as hints (for example to deduplicate), never as proof.
|
|
29
41
|
*/
|
|
30
42
|
interface WebhookHeaders {
|
|
31
43
|
/** Unique event ID (`X-HostWebhook-Event-Id`) */
|
package/dist/index.js
CHANGED
|
@@ -49,7 +49,15 @@ var VerificationError = class extends Error {
|
|
|
49
49
|
this.name = "VerificationError";
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
|
+
function exigirUnSecreto(secret) {
|
|
53
|
+
if (typeof secret !== "string" || secret.trim() === "") {
|
|
54
|
+
throw new VerificationError(
|
|
55
|
+
"The signing secret is empty. Set the signing secret of your endpoint (whsec_\u2026) before verifying"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
52
59
|
function verify(payload, headers, secret, options) {
|
|
60
|
+
exigirUnSecreto(secret);
|
|
53
61
|
const sigHeader = getHeader(headers, "x-hostwebhook-signature");
|
|
54
62
|
if (!sigHeader) {
|
|
55
63
|
throw new VerificationError("Missing X-HostWebhook-Signature header");
|
|
@@ -159,6 +167,7 @@ function parseHeaders(headers) {
|
|
|
159
167
|
|
|
160
168
|
// src/middleware.ts
|
|
161
169
|
function expressMiddleware(secret, options) {
|
|
170
|
+
exigirUnSecreto(secret);
|
|
162
171
|
return (req, res, next) => {
|
|
163
172
|
try {
|
|
164
173
|
verify(req.body, req.headers, secret, options);
|
|
@@ -173,6 +182,7 @@ function expressMiddleware(secret, options) {
|
|
|
173
182
|
};
|
|
174
183
|
}
|
|
175
184
|
function createNestGuard(secret, options) {
|
|
185
|
+
exigirUnSecreto(secret);
|
|
176
186
|
return class HostWebhookGuard {
|
|
177
187
|
canActivate(context) {
|
|
178
188
|
const req = context.switchToHttp().getRequest();
|
|
@@ -182,6 +192,7 @@ function createNestGuard(secret, options) {
|
|
|
182
192
|
};
|
|
183
193
|
}
|
|
184
194
|
function fastifyHook(secret, options) {
|
|
195
|
+
exigirUnSecreto(secret);
|
|
185
196
|
return (req, _reply, done) => {
|
|
186
197
|
try {
|
|
187
198
|
verify(req.body, req.headers, secret, options);
|
|
@@ -410,7 +421,7 @@ doSend_fn = async function(token, payload, options) {
|
|
|
410
421
|
}
|
|
411
422
|
const headers = {
|
|
412
423
|
"Content-Type": options?.contentType ?? "application/json",
|
|
413
|
-
"User-Agent": `HostWebhook-SDK/${"1.6.
|
|
424
|
+
"User-Agent": `HostWebhook-SDK/${"1.6.4"}`,
|
|
414
425
|
"X-HostWebhook-Sent-At": (/* @__PURE__ */ new Date()).toISOString(),
|
|
415
426
|
...tz ? { "X-HostWebhook-Timezone": tz } : {},
|
|
416
427
|
...__privateGet(this, _defaultHeaders),
|
package/dist/index.mjs
CHANGED
|
@@ -15,7 +15,15 @@ var VerificationError = class extends Error {
|
|
|
15
15
|
this.name = "VerificationError";
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
|
+
function exigirUnSecreto(secret) {
|
|
19
|
+
if (typeof secret !== "string" || secret.trim() === "") {
|
|
20
|
+
throw new VerificationError(
|
|
21
|
+
"The signing secret is empty. Set the signing secret of your endpoint (whsec_\u2026) before verifying"
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
18
25
|
function verify(payload, headers, secret, options) {
|
|
26
|
+
exigirUnSecreto(secret);
|
|
19
27
|
const sigHeader = getHeader(headers, "x-hostwebhook-signature");
|
|
20
28
|
if (!sigHeader) {
|
|
21
29
|
throw new VerificationError("Missing X-HostWebhook-Signature header");
|
|
@@ -125,6 +133,7 @@ function parseHeaders(headers) {
|
|
|
125
133
|
|
|
126
134
|
// src/middleware.ts
|
|
127
135
|
function expressMiddleware(secret, options) {
|
|
136
|
+
exigirUnSecreto(secret);
|
|
128
137
|
return (req, res, next) => {
|
|
129
138
|
try {
|
|
130
139
|
verify(req.body, req.headers, secret, options);
|
|
@@ -139,6 +148,7 @@ function expressMiddleware(secret, options) {
|
|
|
139
148
|
};
|
|
140
149
|
}
|
|
141
150
|
function createNestGuard(secret, options) {
|
|
151
|
+
exigirUnSecreto(secret);
|
|
142
152
|
return class HostWebhookGuard {
|
|
143
153
|
canActivate(context) {
|
|
144
154
|
const req = context.switchToHttp().getRequest();
|
|
@@ -148,6 +158,7 @@ function createNestGuard(secret, options) {
|
|
|
148
158
|
};
|
|
149
159
|
}
|
|
150
160
|
function fastifyHook(secret, options) {
|
|
161
|
+
exigirUnSecreto(secret);
|
|
151
162
|
return (req, _reply, done) => {
|
|
152
163
|
try {
|
|
153
164
|
verify(req.body, req.headers, secret, options);
|
|
@@ -376,7 +387,7 @@ doSend_fn = async function(token, payload, options) {
|
|
|
376
387
|
}
|
|
377
388
|
const headers = {
|
|
378
389
|
"Content-Type": options?.contentType ?? "application/json",
|
|
379
|
-
"User-Agent": `HostWebhook-SDK/${"1.6.
|
|
390
|
+
"User-Agent": `HostWebhook-SDK/${"1.6.4"}`,
|
|
380
391
|
"X-HostWebhook-Sent-At": (/* @__PURE__ */ new Date()).toISOString(),
|
|
381
392
|
...tz ? { "X-HostWebhook-Timezone": tz } : {},
|
|
382
393
|
...__privateGet(this, _defaultHeaders),
|
package/llms.txt
CHANGED
|
@@ -80,7 +80,7 @@ try {
|
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
verify(payload, headers, secret, options?) — throws VerificationError if invalid.
|
|
83
|
-
VerifyOptions: { maxAgeSec?: number } — default 300 (5 min). Set 0 to disable replay
|
|
83
|
+
VerifyOptions: { maxAgeSec?: number } — default 300 (5 min). Set 0 to disable the age check. The signature covers only the timestamp and the raw body: the event-id, attempt and chain-* headers are NOT signed, and the timestamp does not stop a replay inside the window (deduplicate by event id). An empty signing secret is refused.
|
|
84
84
|
|
|
85
85
|
## Send Webhooks (sending through HostWebhook)
|
|
86
86
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hostwebhook/node",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.4",
|
|
4
4
|
"description": "Official HostWebhook SDK — verify signatures, parse headers, and send webhooks",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"hmac",
|
|
32
32
|
"retry"
|
|
33
33
|
],
|
|
34
|
-
"license": "
|
|
34
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^25.3.3",
|
|
37
37
|
"tsup": "^8.0.0",
|