hooklatch 0.0.0-stage → 1.0.0
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 +21 -0
- package/README.md +438 -2
- package/dist/adapters/cloudflare.d.ts +11 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -0
- package/dist/adapters/cloudflare.js +27 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/adapters/express.d.ts +9 -0
- package/dist/adapters/express.d.ts.map +1 -0
- package/dist/adapters/express.js +63 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/hono.d.ts +12 -0
- package/dist/adapters/hono.d.ts.map +1 -0
- package/dist/adapters/hono.js +28 -0
- package/dist/adapters/hono.js.map +1 -0
- package/dist/adapters/nextjs.d.ts +6 -0
- package/dist/adapters/nextjs.d.ts.map +1 -0
- package/dist/adapters/nextjs.js +56 -0
- package/dist/adapters/nextjs.js.map +1 -0
- package/dist/adapters/shared.d.ts +8 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +53 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/cjs/adapters/cloudflare.cjs +29 -0
- package/dist/cjs/adapters/cloudflare.d.cts +10 -0
- package/dist/cjs/adapters/express.cjs +65 -0
- package/dist/cjs/adapters/express.d.cts +8 -0
- package/dist/cjs/adapters/hono.cjs +30 -0
- package/dist/cjs/adapters/hono.d.cts +11 -0
- package/dist/cjs/adapters/nextjs.cjs +58 -0
- package/dist/cjs/adapters/nextjs.d.cts +5 -0
- package/dist/cjs/adapters/shared.cjs +58 -0
- package/dist/cjs/adapters/shared.d.cts +7 -0
- package/dist/cjs/core.cjs +30 -0
- package/dist/cjs/core.d.cts +5 -0
- package/dist/cjs/diagnostics/catalog.cjs +341 -0
- package/dist/cjs/diagnostics/catalog.d.cts +19 -0
- package/dist/cjs/diagnostics/index.cjs +90 -0
- package/dist/cjs/diagnostics/index.d.cts +5 -0
- package/dist/cjs/diagnostics/types.cjs +2 -0
- package/dist/cjs/diagnostics/types.d.cts +20 -0
- package/dist/cjs/errors.cjs +13 -0
- package/dist/cjs/errors.d.cts +6 -0
- package/dist/cjs/index.cjs +9 -0
- package/dist/cjs/index.d.cts +3 -0
- package/dist/cjs/internal/body.cjs +50 -0
- package/dist/cjs/internal/body.d.cts +2 -0
- package/dist/cjs/internal/bytes.cjs +43 -0
- package/dist/cjs/internal/bytes.d.cts +4 -0
- package/dist/cjs/internal/crypto.cjs +52 -0
- package/dist/cjs/internal/crypto.d.cts +3 -0
- package/dist/cjs/internal/headers.cjs +72 -0
- package/dist/cjs/internal/headers.d.cts +2 -0
- package/dist/cjs/internal/options.cjs +130 -0
- package/dist/cjs/internal/options.d.cts +7 -0
- package/dist/cjs/internal/prepare.cjs +11 -0
- package/dist/cjs/internal/prepare.d.cts +4 -0
- package/dist/cjs/internal/provider-options.cjs +72 -0
- package/dist/cjs/internal/provider-options.d.cts +6 -0
- package/dist/cjs/internal/provider.cjs +2 -0
- package/dist/cjs/internal/provider.d.cts +39 -0
- package/dist/cjs/internal/verification.cjs +71 -0
- package/dist/cjs/internal/verification.d.cts +12 -0
- package/dist/cjs/providers/clerk.cjs +15 -0
- package/dist/cjs/providers/clerk.d.cts +9 -0
- package/dist/cjs/providers/dodo-payments.cjs +26 -0
- package/dist/cjs/providers/dodo-payments.d.cts +20 -0
- package/dist/cjs/providers/doppler.cjs +71 -0
- package/dist/cjs/providers/doppler.d.cts +37 -0
- package/dist/cjs/providers/fal.cjs +80 -0
- package/dist/cjs/providers/fal.d.cts +13 -0
- package/dist/cjs/providers/github.cjs +52 -0
- package/dist/cjs/providers/github.d.cts +7 -0
- package/dist/cjs/providers/gitlab.cjs +26 -0
- package/dist/cjs/providers/gitlab.d.cts +20 -0
- package/dist/cjs/providers/grafana.cjs +180 -0
- package/dist/cjs/providers/grafana.d.cts +35 -0
- package/dist/cjs/providers/index.cjs +110 -0
- package/dist/cjs/providers/index.d.cts +26 -0
- package/dist/cjs/providers/lemon-squeezy.cjs +36 -0
- package/dist/cjs/providers/lemon-squeezy.d.cts +15 -0
- package/dist/cjs/providers/linear.cjs +58 -0
- package/dist/cjs/providers/linear.d.cts +36 -0
- package/dist/cjs/providers/paddle.cjs +105 -0
- package/dist/cjs/providers/paddle.d.cts +14 -0
- package/dist/cjs/providers/polar.cjs +80 -0
- package/dist/cjs/providers/polar.d.cts +25 -0
- package/dist/cjs/providers/razorpay.cjs +43 -0
- package/dist/cjs/providers/razorpay.d.cts +19 -0
- package/dist/cjs/providers/replicate.cjs +25 -0
- package/dist/cjs/providers/replicate.d.cts +19 -0
- package/dist/cjs/providers/sanity.cjs +161 -0
- package/dist/cjs/providers/sanity.d.cts +38 -0
- package/dist/cjs/providers/sentry.cjs +47 -0
- package/dist/cjs/providers/sentry.d.cts +25 -0
- package/dist/cjs/providers/shared.cjs +49 -0
- package/dist/cjs/providers/shared.d.cts +14 -0
- package/dist/cjs/providers/shopify.cjs +40 -0
- package/dist/cjs/providers/shopify.d.cts +8 -0
- package/dist/cjs/providers/standard-webhooks-protocol.cjs +97 -0
- package/dist/cjs/providers/standard-webhooks-protocol.d.cts +14 -0
- package/dist/cjs/providers/standard-webhooks.cjs +15 -0
- package/dist/cjs/providers/standard-webhooks.d.cts +9 -0
- package/dist/cjs/providers/stripe.cjs +81 -0
- package/dist/cjs/providers/stripe.d.cts +7 -0
- package/dist/cjs/providers/svix.cjs +63 -0
- package/dist/cjs/providers/svix.d.cts +9 -0
- package/dist/cjs/providers/vercel.cjs +39 -0
- package/dist/cjs/providers/vercel.d.cts +15 -0
- package/dist/cjs/providers/workos.cjs +120 -0
- package/dist/cjs/providers/workos.d.cts +32 -0
- package/dist/cjs/types.cjs +2 -0
- package/dist/cjs/types.d.cts +68 -0
- package/dist/cli/args.d.ts +86 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +358 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/filesystem.d.ts +15 -0
- package/dist/cli/filesystem.d.ts.map +1 -0
- package/dist/cli/filesystem.js +170 -0
- package/dist/cli/filesystem.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +64 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/templates.d.ts +13 -0
- package/dist/cli/templates.d.ts.map +1 -0
- package/dist/cli/templates.js +297 -0
- package/dist/cli/templates.js.map +1 -0
- package/dist/core.d.ts +6 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +26 -0
- package/dist/core.js.map +1 -0
- package/dist/diagnostics/catalog.d.ts +20 -0
- package/dist/diagnostics/catalog.d.ts.map +1 -0
- package/dist/diagnostics/catalog.js +335 -0
- package/dist/diagnostics/catalog.js.map +1 -0
- package/dist/diagnostics/index.d.ts +6 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +88 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/diagnostics/types.d.ts +21 -0
- package/dist/diagnostics/types.d.ts.map +1 -0
- package/dist/diagnostics/types.js +2 -0
- package/dist/diagnostics/types.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +10 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/body.d.ts +3 -0
- package/dist/internal/body.d.ts.map +1 -0
- package/dist/internal/body.js +48 -0
- package/dist/internal/body.js.map +1 -0
- package/dist/internal/bytes.d.ts +5 -0
- package/dist/internal/bytes.d.ts.map +1 -0
- package/dist/internal/bytes.js +38 -0
- package/dist/internal/bytes.js.map +1 -0
- package/dist/internal/crypto.d.ts +4 -0
- package/dist/internal/crypto.d.ts.map +1 -0
- package/dist/internal/crypto.js +49 -0
- package/dist/internal/crypto.js.map +1 -0
- package/dist/internal/headers.d.ts +3 -0
- package/dist/internal/headers.d.ts.map +1 -0
- package/dist/internal/headers.js +70 -0
- package/dist/internal/headers.js.map +1 -0
- package/dist/internal/options.d.ts +8 -0
- package/dist/internal/options.d.ts.map +1 -0
- package/dist/internal/options.js +128 -0
- package/dist/internal/options.js.map +1 -0
- package/dist/internal/prepare.d.ts +5 -0
- package/dist/internal/prepare.d.ts.map +1 -0
- package/dist/internal/prepare.js +9 -0
- package/dist/internal/prepare.js.map +1 -0
- package/dist/internal/provider-options.d.ts +7 -0
- package/dist/internal/provider-options.d.ts.map +1 -0
- package/dist/internal/provider-options.js +69 -0
- package/dist/internal/provider-options.js.map +1 -0
- package/dist/internal/provider.d.ts +40 -0
- package/dist/internal/provider.d.ts.map +1 -0
- package/dist/internal/provider.js +2 -0
- package/dist/internal/provider.js.map +1 -0
- package/dist/internal/verification.d.ts +13 -0
- package/dist/internal/verification.d.ts.map +1 -0
- package/dist/internal/verification.js +66 -0
- package/dist/internal/verification.js.map +1 -0
- package/dist/node/adapters/cloudflare.d.ts +1 -0
- package/dist/node/adapters/cloudflare.js +3 -0
- package/dist/node/adapters/express.d.ts +1 -0
- package/dist/node/adapters/express.js +3 -0
- package/dist/node/adapters/hono.d.ts +1 -0
- package/dist/node/adapters/hono.js +3 -0
- package/dist/node/adapters/nextjs.d.ts +1 -0
- package/dist/node/adapters/nextjs.js +3 -0
- package/dist/node/diagnostics/index.d.ts +1 -0
- package/dist/node/diagnostics/index.js +3 -0
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +6 -0
- package/dist/providers/clerk.d.ts +10 -0
- package/dist/providers/clerk.d.ts.map +1 -0
- package/dist/providers/clerk.js +13 -0
- package/dist/providers/clerk.js.map +1 -0
- package/dist/providers/dodo-payments.d.ts +21 -0
- package/dist/providers/dodo-payments.d.ts.map +1 -0
- package/dist/providers/dodo-payments.js +24 -0
- package/dist/providers/dodo-payments.js.map +1 -0
- package/dist/providers/doppler.d.ts +38 -0
- package/dist/providers/doppler.d.ts.map +1 -0
- package/dist/providers/doppler.js +69 -0
- package/dist/providers/doppler.js.map +1 -0
- package/dist/providers/fal.d.ts +14 -0
- package/dist/providers/fal.d.ts.map +1 -0
- package/dist/providers/fal.js +78 -0
- package/dist/providers/fal.js.map +1 -0
- package/dist/providers/github.d.ts +8 -0
- package/dist/providers/github.d.ts.map +1 -0
- package/dist/providers/github.js +50 -0
- package/dist/providers/github.js.map +1 -0
- package/dist/providers/gitlab.d.ts +21 -0
- package/dist/providers/gitlab.d.ts.map +1 -0
- package/dist/providers/gitlab.js +24 -0
- package/dist/providers/gitlab.js.map +1 -0
- package/dist/providers/grafana.d.ts +36 -0
- package/dist/providers/grafana.d.ts.map +1 -0
- package/dist/providers/grafana.js +178 -0
- package/dist/providers/grafana.js.map +1 -0
- package/dist/providers/index.d.ts +27 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +89 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/lemon-squeezy.d.ts +16 -0
- package/dist/providers/lemon-squeezy.d.ts.map +1 -0
- package/dist/providers/lemon-squeezy.js +34 -0
- package/dist/providers/lemon-squeezy.js.map +1 -0
- package/dist/providers/linear.d.ts +37 -0
- package/dist/providers/linear.d.ts.map +1 -0
- package/dist/providers/linear.js +56 -0
- package/dist/providers/linear.js.map +1 -0
- package/dist/providers/paddle.d.ts +15 -0
- package/dist/providers/paddle.d.ts.map +1 -0
- package/dist/providers/paddle.js +103 -0
- package/dist/providers/paddle.js.map +1 -0
- package/dist/providers/polar.d.ts +26 -0
- package/dist/providers/polar.d.ts.map +1 -0
- package/dist/providers/polar.js +78 -0
- package/dist/providers/polar.js.map +1 -0
- package/dist/providers/razorpay.d.ts +20 -0
- package/dist/providers/razorpay.d.ts.map +1 -0
- package/dist/providers/razorpay.js +41 -0
- package/dist/providers/razorpay.js.map +1 -0
- package/dist/providers/replicate.d.ts +20 -0
- package/dist/providers/replicate.d.ts.map +1 -0
- package/dist/providers/replicate.js +23 -0
- package/dist/providers/replicate.js.map +1 -0
- package/dist/providers/sanity.d.ts +39 -0
- package/dist/providers/sanity.d.ts.map +1 -0
- package/dist/providers/sanity.js +159 -0
- package/dist/providers/sanity.js.map +1 -0
- package/dist/providers/sentry.d.ts +26 -0
- package/dist/providers/sentry.d.ts.map +1 -0
- package/dist/providers/sentry.js +45 -0
- package/dist/providers/sentry.js.map +1 -0
- package/dist/providers/shared.d.ts +15 -0
- package/dist/providers/shared.d.ts.map +1 -0
- package/dist/providers/shared.js +40 -0
- package/dist/providers/shared.js.map +1 -0
- package/dist/providers/shopify.d.ts +9 -0
- package/dist/providers/shopify.d.ts.map +1 -0
- package/dist/providers/shopify.js +38 -0
- package/dist/providers/shopify.js.map +1 -0
- package/dist/providers/standard-webhooks-protocol.d.ts +15 -0
- package/dist/providers/standard-webhooks-protocol.d.ts.map +1 -0
- package/dist/providers/standard-webhooks-protocol.js +95 -0
- package/dist/providers/standard-webhooks-protocol.js.map +1 -0
- package/dist/providers/standard-webhooks.d.ts +10 -0
- package/dist/providers/standard-webhooks.d.ts.map +1 -0
- package/dist/providers/standard-webhooks.js +13 -0
- package/dist/providers/standard-webhooks.js.map +1 -0
- package/dist/providers/stripe.d.ts +8 -0
- package/dist/providers/stripe.d.ts.map +1 -0
- package/dist/providers/stripe.js +79 -0
- package/dist/providers/stripe.js.map +1 -0
- package/dist/providers/svix.d.ts +10 -0
- package/dist/providers/svix.d.ts.map +1 -0
- package/dist/providers/svix.js +61 -0
- package/dist/providers/svix.js.map +1 -0
- package/dist/providers/vercel.d.ts +16 -0
- package/dist/providers/vercel.d.ts.map +1 -0
- package/dist/providers/vercel.js +37 -0
- package/dist/providers/vercel.js.map +1 -0
- package/dist/providers/workos.d.ts +33 -0
- package/dist/providers/workos.d.ts.map +1 -0
- package/dist/providers/workos.js +118 -0
- package/dist/providers/workos.js.map +1 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +200 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hooklatch contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,439 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Hooklatch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Verify webhooks. Preserve every byte.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/Its-amanjha/Hooklatch/actions/workflows/ci.yml)
|
|
6
|
+
|
|
7
|
+
Portable webhook signature verification with a zero-dependency core.
|
|
8
|
+
|
|
9
|
+
Hooklatch checks incoming webhook signatures over the exact request bytes
|
|
10
|
+
before your code parses or trusts the payload. It supports Stripe, GitHub,
|
|
11
|
+
Shopify, Standard Webhooks, Clerk, Svix, Razorpay, Paddle Billing, Lemon
|
|
12
|
+
Squeezy, Dodo Payments, GitLab signing tokens, Replicate, WorkOS, Sentry App,
|
|
13
|
+
Doppler signed webhooks, Sanity, Grafana, Polar, Linear, Vercel and fal through
|
|
14
|
+
one asynchronous API,
|
|
15
|
+
with optional framework adapters, diagnostics and a local scaffolding CLI.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- **Node.js >= 22** (ESM and CommonJS; the 22.0.0 floor, later 22.x and 24.x
|
|
21
|
+
were qualified — see [module compatibility](docs/compatibility.md))
|
|
22
|
+
- Zero runtime dependencies in the core
|
|
23
|
+
- Optional peers: `express`, `hono` (only needed for those adapters)
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Install from npm:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install hooklatch
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { verify } from 'hooklatch';
|
|
37
|
+
|
|
38
|
+
const result = await verify(
|
|
39
|
+
{ rawBody, headers }, // exact bytes + headers
|
|
40
|
+
{ provider: 'stripe', secrets: [secret] }, // explicit provider
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
if (!result.ok) {
|
|
44
|
+
// Request-validation failure: safe to log the reason.
|
|
45
|
+
console.warn(result.reason, result.message);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Verified. Parse the payload yourself — verification is not parsing.
|
|
50
|
+
const event = JSON.parse(new TextDecoder().decode(result.rawBody));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
All examples in this README and under `examples/` use synthetic placeholder
|
|
54
|
+
values. Replace them with your real endpoint secrets and provider payloads.
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- **Exact-byte verification.** Signatures are checked over the raw request
|
|
59
|
+
bytes captured before any framework parsing or re-serialization.
|
|
60
|
+
- **Explicit provider selection.** You name the provider; there is no
|
|
61
|
+
auto-detection to misidentify a request.
|
|
62
|
+
- **Credential rotation.** For HMAC providers, pass every active secret; the
|
|
63
|
+
verifier tries signatures against the accept-set. For fal, configure the
|
|
64
|
+
trusted public-key accept-set and expected user ID.
|
|
65
|
+
- **Portable core.** Web Crypto HMAC and Ed25519 with no Node-only imports; runs on
|
|
66
|
+
Node 22+ and in Workers-compatible runtimes.
|
|
67
|
+
- **Framework adapters.** Express, Next.js App Router, Cloudflare Workers,
|
|
68
|
+
and Hono on isolated subpaths.
|
|
69
|
+
|
|
70
|
+
## Supported protocols
|
|
71
|
+
|
|
72
|
+
Verification never parses or validates payload schemas. Algorithms are fixed per
|
|
73
|
+
provider: the first nineteen use HMAC-SHA256, Vercel uses HMAC-SHA1, and fal uses
|
|
74
|
+
Ed25519. Request data cannot select the algorithm.
|
|
75
|
+
|
|
76
|
+
<!-- hooklatch:provider-table:start -->
|
|
77
|
+
| Provider | Algorithm | Signed content | Credentials | Freshness | Identity |
|
|
78
|
+
|----------|-----------|----------------|-------------|-----------|----------|
|
|
79
|
+
| `stripe` | HMAC-SHA256 | `timestamp.body` | Literal secret string | Signed; inclusive tolerance (default 300s) | Signed timestamp only |
|
|
80
|
+
| `github` | HMAC-SHA256 | Body only | Literal secret string | None signed | Unsigned delivery ID |
|
|
81
|
+
| `shopify` | HMAC-SHA256 | Body only | Literal secret string | None signed | Unsigned webhook/event IDs |
|
|
82
|
+
| `standard-webhooks` | HMAC-SHA256 | `id.timestamp.body` | Base64-decoded after `whsec_` strip (24–64 bytes) | Signed; inclusive tolerance (default 300s) | Signed event ID + attempt timestamp |
|
|
83
|
+
| `clerk` | HMAC-SHA256 | `id.timestamp.body` | Base64-decoded after `whsec_` strip (24–64 bytes) | Signed; inclusive tolerance (default 300s) | Signed event ID + attempt timestamp |
|
|
84
|
+
| `svix` | HMAC-SHA256 | `id.timestamp.body` | Base64-decoded after `whsec_` strip (24–64 bytes) | Signed; inclusive tolerance (default 300s) | Signed event ID + attempt timestamp |
|
|
85
|
+
| `razorpay` | HMAC-SHA256 | Body only | Literal secret string | None signed | Optional unsigned event ID |
|
|
86
|
+
| `paddle` | HMAC-SHA256 | `ts:body` | Literal secret string | Signed; default 5s (symmetric inclusive) | Signed timestamp only |
|
|
87
|
+
| `lemon-squeezy` | HMAC-SHA256 | Body only | Literal secret string | None signed | None extracted |
|
|
88
|
+
| `dodo-payments` | HMAC-SHA256 | `id.timestamp.body` | Base64-decoded after optional `whsec_` strip (24–64 bytes) | Signed; default 300s | Signed event ID + timestamp |
|
|
89
|
+
| `gitlab` | HMAC-SHA256 | `id.timestamp.body` | Base64-decoded after optional `whsec_` strip (24–64 bytes) | Signed; default 300s | Signed event ID + timestamp |
|
|
90
|
+
| `replicate` | HMAC-SHA256 | `id.timestamp.body` | Base64-decoded after optional `whsec_` strip (24–64 bytes) | Signed; default 300s | Signed event ID + timestamp |
|
|
91
|
+
| `workos` | HMAC-SHA256 | `t.body` (millisecond `t`) | Literal secret string | Signed; default 180s | Signed timestamp only |
|
|
92
|
+
| `sentry` | HMAC-SHA256 | Body only | Literal secret string | None signed | None extracted |
|
|
93
|
+
| `doppler` | HMAC-SHA256 | Body only | Literal secret string | None signed | None extracted |
|
|
94
|
+
| `sanity` | HMAC-SHA256 | `t.body` (with `.` separator) | Literal secret string | Signed; default 300s (receiver policy) | Signed timestamp only |
|
|
95
|
+
| `grafana` | HMAC-SHA256 | Body (body mode) or `timestamp:body` (timestamped mode) | Literal secret string | Timestamped mode only; default 300s | Signed timestamp in timestamped mode |
|
|
96
|
+
| `polar` | HMAC-SHA256 | `id.timestamp.body` | Base64-decoded (current) or literal UTF-8 (legacy); one mode per accept-set | Signed; default 300s | Signed event ID + timestamp |
|
|
97
|
+
| `linear` | HMAC-SHA256 | Body only | Literal secret string | Body timestamp not extracted or freshness-checked | None extracted |
|
|
98
|
+
| `vercel` | HMAC-SHA1 | Body only | Literal secret string | None signed | None extracted |
|
|
99
|
+
| `fal` | Ed25519 | `requestId\nuserId\ntimestamp\nsha256hex(body)` | Raw 32-byte public keys (1..16) + required `expectedUserId`; no shared secret | Signed; default 300s | Signed timestamp only |
|
|
100
|
+
<!-- hooklatch:provider-table:end -->
|
|
101
|
+
|
|
102
|
+
Clerk ignores canonical `webhook-*` headers. Svix accepts either complete header
|
|
103
|
+
family; if both appear they must be complete and equal. Partial families are
|
|
104
|
+
never assembled together. Standard Webhooks, Clerk and Svix require decoded
|
|
105
|
+
signing keys of 24–64 bytes, as do Dodo, GitLab and Replicate; shorter
|
|
106
|
+
reference-library examples are rejected. GitLab requires a signing token;
|
|
107
|
+
legacy `X-Gitlab-Token` alone is not accepted.
|
|
108
|
+
Linear support authenticates the signature over exact body bytes. It does not
|
|
109
|
+
parse `webhookTimestamp` or enforce freshness. The body claims remain covered by
|
|
110
|
+
the MAC, but Hooklatch does not extract them into identity. `Linear-Timestamp`,
|
|
111
|
+
`Linear-Delivery` and `Linear-Event` headers are unsigned and ignored. Applications
|
|
112
|
+
that need replay checks must validate authenticated body claims after success.
|
|
113
|
+
|
|
114
|
+
See the [provider guide](docs/providers.md) for exact headers, identity fields,
|
|
115
|
+
and protocol-specific limits. Paddle support covers Billing webhooks only.
|
|
116
|
+
|
|
117
|
+
Use endpoint-specific secrets. Providers sharing a signing protocol can accept
|
|
118
|
+
the same signed request when configured with the same key. The `provider`
|
|
119
|
+
option selects verification rules; it does not add cryptographic vendor binding.
|
|
120
|
+
|
|
121
|
+
> **Implementation status (documentation reviewed 2026-10-30):** The core,
|
|
122
|
+
> twenty-one provider descriptors, four adapters, CLI and diagnostics helper are
|
|
123
|
+
> implemented. The last measured acceptance was commit `baa8a74` on 2026-10-28:
|
|
124
|
+
> a clean local `npm run verify` passed 87 suites / 4,161 tests plus the
|
|
125
|
+
> package, type and runtime gates, and exact-SHA CI run
|
|
126
|
+
> [40650684406](https://github.com/Its-amanjha/Hooklatch/actions/runs/40650684406)
|
|
127
|
+
> completed successfully (verify on Node.js 22 and 24; node-floor on Node.js
|
|
128
|
+
> 22.0.0; checked 2026-10-29). Tests use reference vectors and synthetic
|
|
129
|
+
> signatures; no live provider or delivery testing has been performed. This
|
|
130
|
+
> documentation review is dated 2026-10-30 and does not re-run or extend that
|
|
131
|
+
> qualification.
|
|
132
|
+
> Run `npm run verify` for the full local checks. See
|
|
133
|
+
> [verification evidence](docs/verification-evidence.md) for the measured scope,
|
|
134
|
+
> provenance and receipt references, and [CONTRIBUTING.md](CONTRIBUTING.md) for
|
|
135
|
+
> the implemented layout.
|
|
136
|
+
|
|
137
|
+
## Local scaffolding
|
|
138
|
+
|
|
139
|
+
With a built copy of Hooklatch installed, preview one TypeScript handler
|
|
140
|
+
without writing files:
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
npx --no-install hooklatch init webhook --provider clerk --framework nextjs --dry-run
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Remove `--dry-run` to generate it. Existing files are refused. See the
|
|
147
|
+
[CLI reference](docs/cli.md) for paths, environment names and filesystem limits.
|
|
148
|
+
|
|
149
|
+
## Diagnostics
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { diagnose } from 'hooklatch/diagnostics';
|
|
153
|
+
|
|
154
|
+
if (!result.ok) console.warn(diagnose(result));
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The pure helper returns static guidance for a validated reason/provider and
|
|
158
|
+
ignores request messages and payloads. Possible causes are suggestions, not
|
|
159
|
+
proof of a wrong secret or tampering. See [diagnostics](docs/diagnostics.md).
|
|
160
|
+
|
|
161
|
+
## Core API
|
|
162
|
+
|
|
163
|
+
### `verify(request, options)`
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
verify(request: WebhookRequest, options: VerifyOptions): Promise<VerificationResult>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- `rawBody` — the exact request bytes. Never a re-serialized object.
|
|
170
|
+
- `headers` — Web `Headers`, a readonly tuple list, or a record with string
|
|
171
|
+
or string-array values. Lookup is case-insensitive; repeated values are
|
|
172
|
+
comma-combined. Web `Headers` can normalize surrounding whitespace.
|
|
173
|
+
- `providerOptions` — trusted application configuration, required for Grafana,
|
|
174
|
+
optional for Polar, and forbidden for other providers. See configured providers below.
|
|
175
|
+
- `secrets` — HMAC providers only: every active secret, including old and new
|
|
176
|
+
during rotation.
|
|
177
|
+
- `publicKeys` and `expectedUserId` — fal only: trusted raw 32-byte Ed25519
|
|
178
|
+
public keys (1..16) and the required expected signed user ID. Both fields
|
|
179
|
+
belong at the top level; fal forbids `secrets` and `providerOptions`.
|
|
180
|
+
- `toleranceSeconds` — signed-timestamp freshness window. Default `5` for
|
|
181
|
+
Paddle Billing, `180` for WorkOS and `300` for the other timestamped providers.
|
|
182
|
+
The boundary is inclusive: a timestamp exactly `toleranceSeconds` old is
|
|
183
|
+
accepted. `0` means a zero-width window (only exact matches), **not** a
|
|
184
|
+
skip of verification.
|
|
185
|
+
Paddle uses Hooklatch's symmetric window, including future rejection; this
|
|
186
|
+
does not promise exact SDK clock or parser parity. Body-only protocols have
|
|
187
|
+
no authenticated timestamp; signature verification alone does not stop replay.
|
|
188
|
+
- `now` — clock injection for tests; returns epoch seconds and may be fractional.
|
|
189
|
+
WorkOS and Sanity preserve milliseconds in their default clocks; other default
|
|
190
|
+
clocks use whole seconds. Sanity's 300-second freshness window is Hooklatch
|
|
191
|
+
receiver policy, not an established upstream SDK recency default.
|
|
192
|
+
Request-reading APIs and adapters capture the clock before consuming the body;
|
|
193
|
+
freshness is measured at ingress, not at business-processing time.
|
|
194
|
+
- `maxBodyBytes` — body size limit. Default `1048576` (1 MiB). Enforced
|
|
195
|
+
while reading, not just from `Content-Length`.
|
|
196
|
+
|
|
197
|
+
For fal, supply credentials from trusted application configuration:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
const result = await verify(
|
|
201
|
+
{ rawBody, headers },
|
|
202
|
+
{
|
|
203
|
+
provider: 'fal',
|
|
204
|
+
publicKeys: trustedPublicKeys, // 1..16 raw 32-byte Uint8Array values
|
|
205
|
+
expectedUserId: configuredUserId,
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Do not add dummy shared secrets or obtain keys or the expected user ID from
|
|
211
|
+
the incoming request. Signature authentication precedes account matching and
|
|
212
|
+
freshness checks; only the signed timestamp is returned as public identity.
|
|
213
|
+
|
|
214
|
+
### `verifyRequest(request, options)`
|
|
215
|
+
|
|
216
|
+
Consumes a Web `Request` once with bounded reading. Already-consumed bodies
|
|
217
|
+
are setup errors (`HooklatchError`). Returns the same `VerificationResult` shape.
|
|
218
|
+
|
|
219
|
+
### `createVerifier(options)`
|
|
220
|
+
|
|
221
|
+
Returns a preconfigured asynchronous verifier that takes just the request:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
const verify = createVerifier({ provider: 'github', secrets: [secret] });
|
|
225
|
+
const result = await verify({ rawBody, headers });
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Configured providers
|
|
229
|
+
|
|
230
|
+
Grafana requires explicit trusted layout configuration. For example:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { createVerifier } from 'hooklatch';
|
|
234
|
+
|
|
235
|
+
const verifier = createVerifier({
|
|
236
|
+
provider: 'grafana',
|
|
237
|
+
secrets: [secret],
|
|
238
|
+
providerOptions: { mode: 'timestamped', timestampHeader: 'x-alert-timestamp' },
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Body mode signs only the raw body and
|
|
243
|
+
provides no freshness check. Timestamped mode defaults to an inclusive 300-second
|
|
244
|
+
window; neither mode deduplicates requests. Header names are configuration, not
|
|
245
|
+
signed domain separation.
|
|
246
|
+
|
|
247
|
+
Polar defaults to current Base64-decoded keys. Select
|
|
248
|
+
`providerOptions: { secretMode: 'legacy' }` for full literal UTF-8 keys. One mode
|
|
249
|
+
applies to every active secret; mixed-era rotation is unsupported.
|
|
250
|
+
|
|
251
|
+
`VerifyOptions` is now a provider-discriminated union. Existing concrete-provider
|
|
252
|
+
calls remain valid; broad provider objects and interface extension may need
|
|
253
|
+
narrowing. See the [provider guide](docs/providers.md) and
|
|
254
|
+
[type migration notes](docs/diagnostics.md#source-typing-migration-impact).
|
|
255
|
+
|
|
256
|
+
## Result handling
|
|
257
|
+
|
|
258
|
+
Verification failures are **returned**, not thrown. Setup and runtime
|
|
259
|
+
problems are **thrown** as `HooklatchError`.
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import { HooklatchError, verify } from 'hooklatch';
|
|
263
|
+
|
|
264
|
+
// Request failure — an outcome, not an exception.
|
|
265
|
+
const result = await verify(request, options);
|
|
266
|
+
if (!result.ok) {
|
|
267
|
+
// result.reason is one of:
|
|
268
|
+
// 'missing-signature' | 'malformed-signature' | 'unsupported-scheme'
|
|
269
|
+
// | 'invalid-signature' | 'missing-identity' | 'missing-timestamp'
|
|
270
|
+
// | 'timestamp-expired' | 'timestamp-future' | 'body-too-large'
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Setup failure — thrown, safe to let crash at boot time.
|
|
274
|
+
try {
|
|
275
|
+
const verify = createVerifier({ provider: 'nope', secrets: [] });
|
|
276
|
+
} catch (err) {
|
|
277
|
+
if (err instanceof HooklatchError) console.warn(err.code);
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
| Failure | Meaning |
|
|
282
|
+
|---------|---------|
|
|
283
|
+
| `missing-signature` | Expected signature header absent |
|
|
284
|
+
| `malformed-signature` | Header present but unparseable |
|
|
285
|
+
| `unsupported-scheme` | Only legacy/unknown schemes present |
|
|
286
|
+
| `invalid-signature` | Well-formed, matches no configured secret |
|
|
287
|
+
| `missing-identity` | Required metadata header absent |
|
|
288
|
+
| `missing-timestamp` | Signed-timestamp scheme lacks timestamp |
|
|
289
|
+
| `timestamp-expired` | Older than tolerance window |
|
|
290
|
+
| `timestamp-future` | Newer than tolerance window |
|
|
291
|
+
| `body-too-large` | Exceeds `maxBodyBytes` |
|
|
292
|
+
|
|
293
|
+
## Verified is not parsed
|
|
294
|
+
|
|
295
|
+
A successful result says the bytes are authentic. It says nothing about the
|
|
296
|
+
payload's shape. Decode and validate with your own tools:
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
import { z } from 'zod';
|
|
300
|
+
|
|
301
|
+
const result = await verify(request, options);
|
|
302
|
+
if (!result.ok) return;
|
|
303
|
+
|
|
304
|
+
const event = StripeEventSchema.parse(
|
|
305
|
+
JSON.parse(new TextDecoder().decode(result.rawBody)),
|
|
306
|
+
);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Body size limits
|
|
310
|
+
|
|
311
|
+
`maxBodyBytes` (default 1 MiB) is enforced while reading streams and before
|
|
312
|
+
copying raw inputs. Exceeding it returns `{ ok: false, reason:
|
|
313
|
+
'body-too-large' }` for request verification, or `HooklatchError` for invalid
|
|
314
|
+
limit configuration. Platform limits (e.g. Workers plan limits) may reject
|
|
315
|
+
earlier.
|
|
316
|
+
|
|
317
|
+
## Framework adapters
|
|
318
|
+
|
|
319
|
+
Adapters live on isolated subpaths and import their framework as an optional
|
|
320
|
+
peer dependency:
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
import { createWebhookMiddleware } from 'hooklatch/express';
|
|
324
|
+
import { createWebhookMiddleware } from 'hooklatch/hono';
|
|
325
|
+
import { createWebhookHandler } from 'hooklatch/nextjs';
|
|
326
|
+
import { createWebhookHandler } from 'hooklatch/cloudflare';
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Adapter behavior (contract v1):
|
|
330
|
+
|
|
331
|
+
- Verification failures respond **400**; size violations **413**;
|
|
332
|
+
setup/runtime failures **500** with a generic response. Core reasons remain
|
|
333
|
+
available for safe application logging.
|
|
334
|
+
- User handler errors follow the host framework; they are never reported as
|
|
335
|
+
signature failures.
|
|
336
|
+
- Success runs the handler/next stage without acknowledging work early.
|
|
337
|
+
|
|
338
|
+
### Express
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
import { createWebhookMiddleware } from 'hooklatch/express';
|
|
342
|
+
|
|
343
|
+
app.post(
|
|
344
|
+
'/webhooks/stripe',
|
|
345
|
+
createWebhookMiddleware({ provider: 'stripe', secrets: [secret] }),
|
|
346
|
+
(req, res) => {
|
|
347
|
+
const success = req.webhook;
|
|
348
|
+
if (!success) return res.sendStatus(500);
|
|
349
|
+
const event = JSON.parse(new TextDecoder().decode(success.rawBody));
|
|
350
|
+
res.json({ received: true });
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Mount the middleware **before** any JSON body parser. The adapter reads the
|
|
356
|
+
existing raw `Buffer`/`Uint8Array` or an unconsumed request stream; it never
|
|
357
|
+
reconstructs bytes from a parsed object. Unsupported compressed bodies are
|
|
358
|
+
rejected explicitly.
|
|
359
|
+
|
|
360
|
+
### Hono
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
import { Hono } from 'hono';
|
|
364
|
+
import { createWebhookMiddleware, type WebhookVariables } from 'hooklatch/hono';
|
|
365
|
+
|
|
366
|
+
const app = new Hono<{ Variables: WebhookVariables }>();
|
|
367
|
+
|
|
368
|
+
app.post(
|
|
369
|
+
'/webhooks/github',
|
|
370
|
+
createWebhookMiddleware({ provider: 'github', secrets: [secret] }),
|
|
371
|
+
async (c) => {
|
|
372
|
+
const success = c.get('webhook'); // VerificationSuccess
|
|
373
|
+
const event = JSON.parse(new TextDecoder().decode(success.rawBody));
|
|
374
|
+
return c.json({ received: true });
|
|
375
|
+
},
|
|
376
|
+
);
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Next.js App Router
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
// app/webhooks/stripe/route.ts
|
|
383
|
+
import { createWebhookHandler } from 'hooklatch/nextjs';
|
|
384
|
+
|
|
385
|
+
export const POST = createWebhookHandler({
|
|
386
|
+
provider: 'stripe',
|
|
387
|
+
secrets: [process.env.STRIPE_SECRET!],
|
|
388
|
+
handler: async (request, success) => {
|
|
389
|
+
const event = JSON.parse(new TextDecoder().decode(success.rawBody));
|
|
390
|
+
return Response.json({ received: true });
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
The original request body is consumed during verification; read
|
|
396
|
+
`success.rawBody`, not the request body.
|
|
397
|
+
|
|
398
|
+
### Cloudflare Workers
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
import { createWebhookHandler } from 'hooklatch/cloudflare';
|
|
402
|
+
|
|
403
|
+
const fetch = createWebhookHandler<{ GITHUB_SECRET: string }>({
|
|
404
|
+
options: (env) => ({ provider: 'github', secrets: [env.GITHUB_SECRET] }),
|
|
405
|
+
handler: async (request, env, ctx, success) => {
|
|
406
|
+
const event = JSON.parse(new TextDecoder().decode(success.rawBody));
|
|
407
|
+
return Response.json({ received: true });
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
export default { fetch };
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
`options` receives `env` so bindings are resolved explicitly.
|
|
415
|
+
|
|
416
|
+
## Roadmap
|
|
417
|
+
|
|
418
|
+
- Future delivery integrations (queueing, retries, DLQ) are planned but not
|
|
419
|
+
part of this release. They will be designed separately and will not couple
|
|
420
|
+
to the verification API.
|
|
421
|
+
|
|
422
|
+
## Security
|
|
423
|
+
|
|
424
|
+
See `SECURITY.md` for the vulnerability reporting policy.
|
|
425
|
+
|
|
426
|
+
## License
|
|
427
|
+
|
|
428
|
+
MIT — see `LICENSE`.
|
|
429
|
+
|
|
430
|
+
Reference test vectors come from [GitHub's webhook verification documentation](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries)
|
|
431
|
+
and the [Standard Webhooks JavaScript test suite at a pinned revision](https://github.com/standard-webhooks/standard-webhooks/blob/7537d2a2d3d52d8f2e0ecd12527af4a9307fd81b/libraries/javascript/src/webhook.test.ts).
|
|
432
|
+
Additional reference tuples come from the [Sanity toolkit tests at a pinned revision](https://github.com/sanity-io/webhook-toolkit/blob/e0c211ed6cd80465a70829e335c9e219f37839ba/test/signature.test.ts).
|
|
433
|
+
The [Paddle Node SDK validator tests](https://github.com/PaddleHQ/paddle-node-sdk/blob/651261beddfc65ba5f861729dca17044cbeffb5f/src/__tests__/notifications/webhooks-validator.node.test.ts)
|
|
434
|
+
also supply two published reference tuples. These are external reference material;
|
|
435
|
+
locally generated cases are separately labelled synthetic.
|
|
436
|
+
The Standard Webhooks repository includes an
|
|
437
|
+
[MIT notice under `libraries/`](https://github.com/standard-webhooks/standard-webhooks/blob/7537d2a2d3d52d8f2e0ecd12527af4a9307fd81b/libraries/LICENSE).
|
|
438
|
+
See [source and history notes](https://github.com/Its-amanjha/Hooklatch/blob/main/CONTRIBUTING.md#source-and-history-notes)
|
|
439
|
+
for attribution and review limits.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { VerificationSuccess, VerifyOptions } from '../types.js';
|
|
2
|
+
export interface ExecutionContextLike {
|
|
3
|
+
waitUntil?(promise: Promise<unknown>): void;
|
|
4
|
+
passThroughOnException?(): void;
|
|
5
|
+
}
|
|
6
|
+
export interface WorkerWebhookHandlerOptions<Env = unknown> {
|
|
7
|
+
readonly options: (env: Env) => VerifyOptions;
|
|
8
|
+
readonly handler: (request: Request, env: Env, ctx: ExecutionContextLike, success: VerificationSuccess) => Response | Promise<Response>;
|
|
9
|
+
}
|
|
10
|
+
export declare function createWebhookHandler<Env = unknown>(options: WorkerWebhookHandlerOptions<Env>): (request: Request, env: Env, ctx: ExecutionContextLike) => Promise<Response>;
|
|
11
|
+
//# sourceMappingURL=cloudflare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5C,sBAAsB,CAAC,IAAI,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,2BAA2B,CAAC,GAAG,GAAG,OAAO;IACxD,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,aAAa,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,CAChB,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,oBAAoB,EACzB,OAAO,EAAE,mBAAmB,KACzB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACnC;AAED,wBAAgB,oBAAoB,CAAC,GAAG,GAAG,OAAO,EAChD,OAAO,EAAE,2BAA2B,CAAC,GAAG,CAAC,GACxC,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,oBAAoB,KAAK,OAAO,CAAC,QAAQ,CAAC,CA8B9E"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { verifyRequest } from '../index.js';
|
|
2
|
+
export function createWebhookHandler(options) {
|
|
3
|
+
return async (request, env, ctx) => {
|
|
4
|
+
const encoding = request.headers.get('content-encoding');
|
|
5
|
+
if (encoding && encoding.toLowerCase() !== 'identity') {
|
|
6
|
+
return new Response('Bad Request', { status: 400 });
|
|
7
|
+
}
|
|
8
|
+
let verification;
|
|
9
|
+
try {
|
|
10
|
+
const verifyOptions = options.options(env);
|
|
11
|
+
const result = await verifyRequest(request, verifyOptions);
|
|
12
|
+
if (!result.ok) {
|
|
13
|
+
if (result.reason === 'body-too-large') {
|
|
14
|
+
return new Response('Payload Too Large', { status: 413 });
|
|
15
|
+
}
|
|
16
|
+
return new Response('Bad Request', { status: 400 });
|
|
17
|
+
}
|
|
18
|
+
verification = result;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// This boundary contains setup/verification only, never the user handler.
|
|
22
|
+
return new Response('Internal Server Error', { status: 500 });
|
|
23
|
+
}
|
|
24
|
+
return await options.handler(request, env, ctx, verification);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAkB5C,MAAM,UAAU,oBAAoB,CAClC,OAAyC;IAEzC,OAAO,KAAK,EAAE,OAAgB,EAAE,GAAQ,EAAE,GAAyB,EAAqB,EAAE;QACxF,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAEzD,IAAI,QAAQ,IAAI,QAAQ,CAAC,WAAW,EAAE,KAAK,UAAU,EAAE,CAAC;YACtD,OAAO,IAAI,QAAQ,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QACtD,CAAC;QAED,IAAI,YAAkC,CAAC;QAEvC,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC3C,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;YAE3D,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,IAAI,MAAM,CAAC,MAAM,KAAK,gBAAgB,EAAE,CAAC;oBACvC,OAAO,IAAI,QAAQ,CAAC,mBAAmB,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;gBAC5D,CAAC;gBAED,OAAO,IAAI,QAAQ,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YACtD,CAAC;YAED,YAAY,GAAG,MAAM,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,0EAA0E;YAC1E,OAAO,IAAI,QAAQ,CAAC,uBAAuB,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAChE,CAAC;QAED,OAAO,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC;IAChE,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RequestHandler } from 'express';
|
|
2
|
+
import type { VerificationSuccess, VerifyOptions } from '../types.js';
|
|
3
|
+
declare module 'express-serve-static-core' {
|
|
4
|
+
interface Request {
|
|
5
|
+
webhook?: VerificationSuccess;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export declare function createWebhookMiddleware(options: VerifyOptions): RequestHandler;
|
|
9
|
+
//# sourceMappingURL=express.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAM/E,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,QAAQ,2BAA2B,CAAC;IACzC,UAAU,OAAO;QACf,OAAO,CAAC,EAAE,mBAAmB,CAAC;KAC/B;CACF;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc,CAwE9E"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { HttpError, readNodeStream } from './shared.js';
|
|
2
|
+
import { HooklatchError } from '../errors.js';
|
|
3
|
+
import { prepareVerification } from '../internal/prepare.js';
|
|
4
|
+
import { readClock, verifyPrepared } from '../internal/verification.js';
|
|
5
|
+
import { normalizeHeaders } from '../internal/headers.js';
|
|
6
|
+
export function createWebhookMiddleware(options) {
|
|
7
|
+
return async (req, res, next) => {
|
|
8
|
+
try {
|
|
9
|
+
const prepared = prepareVerification(options);
|
|
10
|
+
const normalized = prepared.options;
|
|
11
|
+
const receivedAt = readClock(prepared);
|
|
12
|
+
const headers = [...normalizeHeaders(req.headers)];
|
|
13
|
+
const maxBytes = normalized.maxBodyBytes;
|
|
14
|
+
const encodingHeader = req.headers['content-encoding'];
|
|
15
|
+
const encoding = Array.isArray(encodingHeader) ? encodingHeader.join(',') : encodingHeader;
|
|
16
|
+
if (encoding && encoding.toLowerCase() !== 'identity') {
|
|
17
|
+
res.status(400).send('Bad Request');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const body = req.body;
|
|
21
|
+
let rawBody;
|
|
22
|
+
if (body instanceof Uint8Array) {
|
|
23
|
+
if (body.byteLength > maxBytes) {
|
|
24
|
+
res.status(413).send('Payload Too Large');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
rawBody = new Uint8Array(body);
|
|
28
|
+
}
|
|
29
|
+
else if (body !== undefined && body !== null) {
|
|
30
|
+
res.status(500).send('Internal Server Error');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
if (req.readableEnded || req.readableDidRead || req.destroyed || req.readable === false) {
|
|
35
|
+
res.status(500).send('Internal Server Error');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
rawBody = await readNodeStream(req, maxBytes);
|
|
39
|
+
}
|
|
40
|
+
const result = await verifyPrepared({ rawBody, headers }, prepared, receivedAt);
|
|
41
|
+
if (!result.ok) {
|
|
42
|
+
const status = result.reason === 'body-too-large' ? 413 : 400;
|
|
43
|
+
res.status(status).send(status === 413 ? 'Payload Too Large' : 'Bad Request');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
req.webhook = result;
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (err instanceof HttpError) {
|
|
50
|
+
res.status(err.status).send(err.message);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (err instanceof HooklatchError) {
|
|
54
|
+
res.status(500).send('Internal Server Error');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
next(err);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
next();
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { VerificationSuccess, VerifyOptions } from '../types.js';
|
|
3
|
+
declare module 'hono' {
|
|
4
|
+
interface ContextVariableMap {
|
|
5
|
+
webhook?: VerificationSuccess;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export type WebhookVariables = {
|
|
9
|
+
webhook: VerificationSuccess;
|
|
10
|
+
};
|
|
11
|
+
export declare function createWebhookMiddleware(options: VerifyOptions): MiddlewareHandler;
|
|
12
|
+
//# sourceMappingURL=hono.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,QAAQ,MAAM,CAAC;IACpB,UAAU,kBAAkB;QAC1B,OAAO,CAAC,EAAE,mBAAmB,CAAC;KAC/B;CACF;AAED,MAAM,MAAM,gBAAgB,GAAG;IAAE,OAAO,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAEhE,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,aAAa,GAAG,iBAAiB,CA8BjF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { HooklatchError } from '../errors.js';
|
|
2
|
+
import { verifyRequest } from '../index.js';
|
|
3
|
+
export function createWebhookMiddleware(options) {
|
|
4
|
+
return async (c, next) => {
|
|
5
|
+
try {
|
|
6
|
+
const encoding = c.req.raw.headers.get('content-encoding');
|
|
7
|
+
if (encoding && encoding.toLowerCase() !== 'identity') {
|
|
8
|
+
return c.text('Bad Request', 400);
|
|
9
|
+
}
|
|
10
|
+
const result = await verifyRequest(c.req.raw, options);
|
|
11
|
+
if (!result.ok) {
|
|
12
|
+
if (result.reason === 'body-too-large') {
|
|
13
|
+
return c.text('Payload Too Large', 413);
|
|
14
|
+
}
|
|
15
|
+
return c.text('Bad Request', 400);
|
|
16
|
+
}
|
|
17
|
+
c.set('webhook', result);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
if (err instanceof HooklatchError) {
|
|
21
|
+
return c.text('Internal Server Error', 500);
|
|
22
|
+
}
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
await next();
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=hono.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.js","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAW5C,MAAM,UAAU,uBAAuB,CAAC,OAAsB;IAC5D,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;YAE3D,IAAI,QAAQ,IAAI,QAAQ,CAAC,WAAW,EAAE,KAAK,UAAU,EAAE,CAAC;gBACtD,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;YACpC,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAEvD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,IAAI,MAAM,CAAC,MAAM,KAAK,gBAAgB,EAAE,CAAC;oBACvC,OAAO,CAAC,CAAC,IAAI,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;gBAC1C,CAAC;gBAED,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;YACpC,CAAC;YAED,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;gBAClC,OAAO,CAAC,CAAC,IAAI,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;YAC9C,CAAC;YAED,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { VerificationSuccess, VerifyOptions } from '../types.js';
|
|
2
|
+
export type NextWebhookHandlerOptions = VerifyOptions & {
|
|
3
|
+
readonly handler: (request: Request, success: VerificationSuccess) => Response | Promise<Response>;
|
|
4
|
+
};
|
|
5
|
+
export declare function createWebhookHandler(options: NextWebhookHandlerOptions): (request: Request) => Promise<Response>;
|
|
6
|
+
//# sourceMappingURL=nextjs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nextjs.d.ts","sourceRoot":"","sources":["../../src/adapters/nextjs.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,MAAM,yBAAyB,GAAG,aAAa,GAAG;IACtD,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACpG,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,yBAAyB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA6BhH"}
|