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.
Files changed (301) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +438 -2
  3. package/dist/adapters/cloudflare.d.ts +11 -0
  4. package/dist/adapters/cloudflare.d.ts.map +1 -0
  5. package/dist/adapters/cloudflare.js +27 -0
  6. package/dist/adapters/cloudflare.js.map +1 -0
  7. package/dist/adapters/express.d.ts +9 -0
  8. package/dist/adapters/express.d.ts.map +1 -0
  9. package/dist/adapters/express.js +63 -0
  10. package/dist/adapters/express.js.map +1 -0
  11. package/dist/adapters/hono.d.ts +12 -0
  12. package/dist/adapters/hono.d.ts.map +1 -0
  13. package/dist/adapters/hono.js +28 -0
  14. package/dist/adapters/hono.js.map +1 -0
  15. package/dist/adapters/nextjs.d.ts +6 -0
  16. package/dist/adapters/nextjs.d.ts.map +1 -0
  17. package/dist/adapters/nextjs.js +56 -0
  18. package/dist/adapters/nextjs.js.map +1 -0
  19. package/dist/adapters/shared.d.ts +8 -0
  20. package/dist/adapters/shared.d.ts.map +1 -0
  21. package/dist/adapters/shared.js +53 -0
  22. package/dist/adapters/shared.js.map +1 -0
  23. package/dist/cjs/adapters/cloudflare.cjs +29 -0
  24. package/dist/cjs/adapters/cloudflare.d.cts +10 -0
  25. package/dist/cjs/adapters/express.cjs +65 -0
  26. package/dist/cjs/adapters/express.d.cts +8 -0
  27. package/dist/cjs/adapters/hono.cjs +30 -0
  28. package/dist/cjs/adapters/hono.d.cts +11 -0
  29. package/dist/cjs/adapters/nextjs.cjs +58 -0
  30. package/dist/cjs/adapters/nextjs.d.cts +5 -0
  31. package/dist/cjs/adapters/shared.cjs +58 -0
  32. package/dist/cjs/adapters/shared.d.cts +7 -0
  33. package/dist/cjs/core.cjs +30 -0
  34. package/dist/cjs/core.d.cts +5 -0
  35. package/dist/cjs/diagnostics/catalog.cjs +341 -0
  36. package/dist/cjs/diagnostics/catalog.d.cts +19 -0
  37. package/dist/cjs/diagnostics/index.cjs +90 -0
  38. package/dist/cjs/diagnostics/index.d.cts +5 -0
  39. package/dist/cjs/diagnostics/types.cjs +2 -0
  40. package/dist/cjs/diagnostics/types.d.cts +20 -0
  41. package/dist/cjs/errors.cjs +13 -0
  42. package/dist/cjs/errors.d.cts +6 -0
  43. package/dist/cjs/index.cjs +9 -0
  44. package/dist/cjs/index.d.cts +3 -0
  45. package/dist/cjs/internal/body.cjs +50 -0
  46. package/dist/cjs/internal/body.d.cts +2 -0
  47. package/dist/cjs/internal/bytes.cjs +43 -0
  48. package/dist/cjs/internal/bytes.d.cts +4 -0
  49. package/dist/cjs/internal/crypto.cjs +52 -0
  50. package/dist/cjs/internal/crypto.d.cts +3 -0
  51. package/dist/cjs/internal/headers.cjs +72 -0
  52. package/dist/cjs/internal/headers.d.cts +2 -0
  53. package/dist/cjs/internal/options.cjs +130 -0
  54. package/dist/cjs/internal/options.d.cts +7 -0
  55. package/dist/cjs/internal/prepare.cjs +11 -0
  56. package/dist/cjs/internal/prepare.d.cts +4 -0
  57. package/dist/cjs/internal/provider-options.cjs +72 -0
  58. package/dist/cjs/internal/provider-options.d.cts +6 -0
  59. package/dist/cjs/internal/provider.cjs +2 -0
  60. package/dist/cjs/internal/provider.d.cts +39 -0
  61. package/dist/cjs/internal/verification.cjs +71 -0
  62. package/dist/cjs/internal/verification.d.cts +12 -0
  63. package/dist/cjs/providers/clerk.cjs +15 -0
  64. package/dist/cjs/providers/clerk.d.cts +9 -0
  65. package/dist/cjs/providers/dodo-payments.cjs +26 -0
  66. package/dist/cjs/providers/dodo-payments.d.cts +20 -0
  67. package/dist/cjs/providers/doppler.cjs +71 -0
  68. package/dist/cjs/providers/doppler.d.cts +37 -0
  69. package/dist/cjs/providers/fal.cjs +80 -0
  70. package/dist/cjs/providers/fal.d.cts +13 -0
  71. package/dist/cjs/providers/github.cjs +52 -0
  72. package/dist/cjs/providers/github.d.cts +7 -0
  73. package/dist/cjs/providers/gitlab.cjs +26 -0
  74. package/dist/cjs/providers/gitlab.d.cts +20 -0
  75. package/dist/cjs/providers/grafana.cjs +180 -0
  76. package/dist/cjs/providers/grafana.d.cts +35 -0
  77. package/dist/cjs/providers/index.cjs +110 -0
  78. package/dist/cjs/providers/index.d.cts +26 -0
  79. package/dist/cjs/providers/lemon-squeezy.cjs +36 -0
  80. package/dist/cjs/providers/lemon-squeezy.d.cts +15 -0
  81. package/dist/cjs/providers/linear.cjs +58 -0
  82. package/dist/cjs/providers/linear.d.cts +36 -0
  83. package/dist/cjs/providers/paddle.cjs +105 -0
  84. package/dist/cjs/providers/paddle.d.cts +14 -0
  85. package/dist/cjs/providers/polar.cjs +80 -0
  86. package/dist/cjs/providers/polar.d.cts +25 -0
  87. package/dist/cjs/providers/razorpay.cjs +43 -0
  88. package/dist/cjs/providers/razorpay.d.cts +19 -0
  89. package/dist/cjs/providers/replicate.cjs +25 -0
  90. package/dist/cjs/providers/replicate.d.cts +19 -0
  91. package/dist/cjs/providers/sanity.cjs +161 -0
  92. package/dist/cjs/providers/sanity.d.cts +38 -0
  93. package/dist/cjs/providers/sentry.cjs +47 -0
  94. package/dist/cjs/providers/sentry.d.cts +25 -0
  95. package/dist/cjs/providers/shared.cjs +49 -0
  96. package/dist/cjs/providers/shared.d.cts +14 -0
  97. package/dist/cjs/providers/shopify.cjs +40 -0
  98. package/dist/cjs/providers/shopify.d.cts +8 -0
  99. package/dist/cjs/providers/standard-webhooks-protocol.cjs +97 -0
  100. package/dist/cjs/providers/standard-webhooks-protocol.d.cts +14 -0
  101. package/dist/cjs/providers/standard-webhooks.cjs +15 -0
  102. package/dist/cjs/providers/standard-webhooks.d.cts +9 -0
  103. package/dist/cjs/providers/stripe.cjs +81 -0
  104. package/dist/cjs/providers/stripe.d.cts +7 -0
  105. package/dist/cjs/providers/svix.cjs +63 -0
  106. package/dist/cjs/providers/svix.d.cts +9 -0
  107. package/dist/cjs/providers/vercel.cjs +39 -0
  108. package/dist/cjs/providers/vercel.d.cts +15 -0
  109. package/dist/cjs/providers/workos.cjs +120 -0
  110. package/dist/cjs/providers/workos.d.cts +32 -0
  111. package/dist/cjs/types.cjs +2 -0
  112. package/dist/cjs/types.d.cts +68 -0
  113. package/dist/cli/args.d.ts +86 -0
  114. package/dist/cli/args.d.ts.map +1 -0
  115. package/dist/cli/args.js +358 -0
  116. package/dist/cli/args.js.map +1 -0
  117. package/dist/cli/filesystem.d.ts +15 -0
  118. package/dist/cli/filesystem.d.ts.map +1 -0
  119. package/dist/cli/filesystem.js +170 -0
  120. package/dist/cli/filesystem.js.map +1 -0
  121. package/dist/cli/index.d.ts +3 -0
  122. package/dist/cli/index.d.ts.map +1 -0
  123. package/dist/cli/index.js +64 -0
  124. package/dist/cli/index.js.map +1 -0
  125. package/dist/cli/templates.d.ts +13 -0
  126. package/dist/cli/templates.d.ts.map +1 -0
  127. package/dist/cli/templates.js +297 -0
  128. package/dist/cli/templates.js.map +1 -0
  129. package/dist/core.d.ts +6 -0
  130. package/dist/core.d.ts.map +1 -0
  131. package/dist/core.js +26 -0
  132. package/dist/core.js.map +1 -0
  133. package/dist/diagnostics/catalog.d.ts +20 -0
  134. package/dist/diagnostics/catalog.d.ts.map +1 -0
  135. package/dist/diagnostics/catalog.js +335 -0
  136. package/dist/diagnostics/catalog.js.map +1 -0
  137. package/dist/diagnostics/index.d.ts +6 -0
  138. package/dist/diagnostics/index.d.ts.map +1 -0
  139. package/dist/diagnostics/index.js +88 -0
  140. package/dist/diagnostics/index.js.map +1 -0
  141. package/dist/diagnostics/types.d.ts +21 -0
  142. package/dist/diagnostics/types.d.ts.map +1 -0
  143. package/dist/diagnostics/types.js +2 -0
  144. package/dist/diagnostics/types.js.map +1 -0
  145. package/dist/errors.d.ts +7 -0
  146. package/dist/errors.d.ts.map +1 -0
  147. package/dist/errors.js +10 -0
  148. package/dist/errors.js.map +1 -0
  149. package/dist/index.d.ts +4 -0
  150. package/dist/index.d.ts.map +1 -0
  151. package/dist/index.js +3 -0
  152. package/dist/index.js.map +1 -0
  153. package/dist/internal/body.d.ts +3 -0
  154. package/dist/internal/body.d.ts.map +1 -0
  155. package/dist/internal/body.js +48 -0
  156. package/dist/internal/body.js.map +1 -0
  157. package/dist/internal/bytes.d.ts +5 -0
  158. package/dist/internal/bytes.d.ts.map +1 -0
  159. package/dist/internal/bytes.js +38 -0
  160. package/dist/internal/bytes.js.map +1 -0
  161. package/dist/internal/crypto.d.ts +4 -0
  162. package/dist/internal/crypto.d.ts.map +1 -0
  163. package/dist/internal/crypto.js +49 -0
  164. package/dist/internal/crypto.js.map +1 -0
  165. package/dist/internal/headers.d.ts +3 -0
  166. package/dist/internal/headers.d.ts.map +1 -0
  167. package/dist/internal/headers.js +70 -0
  168. package/dist/internal/headers.js.map +1 -0
  169. package/dist/internal/options.d.ts +8 -0
  170. package/dist/internal/options.d.ts.map +1 -0
  171. package/dist/internal/options.js +128 -0
  172. package/dist/internal/options.js.map +1 -0
  173. package/dist/internal/prepare.d.ts +5 -0
  174. package/dist/internal/prepare.d.ts.map +1 -0
  175. package/dist/internal/prepare.js +9 -0
  176. package/dist/internal/prepare.js.map +1 -0
  177. package/dist/internal/provider-options.d.ts +7 -0
  178. package/dist/internal/provider-options.d.ts.map +1 -0
  179. package/dist/internal/provider-options.js +69 -0
  180. package/dist/internal/provider-options.js.map +1 -0
  181. package/dist/internal/provider.d.ts +40 -0
  182. package/dist/internal/provider.d.ts.map +1 -0
  183. package/dist/internal/provider.js +2 -0
  184. package/dist/internal/provider.js.map +1 -0
  185. package/dist/internal/verification.d.ts +13 -0
  186. package/dist/internal/verification.d.ts.map +1 -0
  187. package/dist/internal/verification.js +66 -0
  188. package/dist/internal/verification.js.map +1 -0
  189. package/dist/node/adapters/cloudflare.d.ts +1 -0
  190. package/dist/node/adapters/cloudflare.js +3 -0
  191. package/dist/node/adapters/express.d.ts +1 -0
  192. package/dist/node/adapters/express.js +3 -0
  193. package/dist/node/adapters/hono.d.ts +1 -0
  194. package/dist/node/adapters/hono.js +3 -0
  195. package/dist/node/adapters/nextjs.d.ts +1 -0
  196. package/dist/node/adapters/nextjs.js +3 -0
  197. package/dist/node/diagnostics/index.d.ts +1 -0
  198. package/dist/node/diagnostics/index.js +3 -0
  199. package/dist/node/index.d.ts +1 -0
  200. package/dist/node/index.js +6 -0
  201. package/dist/providers/clerk.d.ts +10 -0
  202. package/dist/providers/clerk.d.ts.map +1 -0
  203. package/dist/providers/clerk.js +13 -0
  204. package/dist/providers/clerk.js.map +1 -0
  205. package/dist/providers/dodo-payments.d.ts +21 -0
  206. package/dist/providers/dodo-payments.d.ts.map +1 -0
  207. package/dist/providers/dodo-payments.js +24 -0
  208. package/dist/providers/dodo-payments.js.map +1 -0
  209. package/dist/providers/doppler.d.ts +38 -0
  210. package/dist/providers/doppler.d.ts.map +1 -0
  211. package/dist/providers/doppler.js +69 -0
  212. package/dist/providers/doppler.js.map +1 -0
  213. package/dist/providers/fal.d.ts +14 -0
  214. package/dist/providers/fal.d.ts.map +1 -0
  215. package/dist/providers/fal.js +78 -0
  216. package/dist/providers/fal.js.map +1 -0
  217. package/dist/providers/github.d.ts +8 -0
  218. package/dist/providers/github.d.ts.map +1 -0
  219. package/dist/providers/github.js +50 -0
  220. package/dist/providers/github.js.map +1 -0
  221. package/dist/providers/gitlab.d.ts +21 -0
  222. package/dist/providers/gitlab.d.ts.map +1 -0
  223. package/dist/providers/gitlab.js +24 -0
  224. package/dist/providers/gitlab.js.map +1 -0
  225. package/dist/providers/grafana.d.ts +36 -0
  226. package/dist/providers/grafana.d.ts.map +1 -0
  227. package/dist/providers/grafana.js +178 -0
  228. package/dist/providers/grafana.js.map +1 -0
  229. package/dist/providers/index.d.ts +27 -0
  230. package/dist/providers/index.d.ts.map +1 -0
  231. package/dist/providers/index.js +89 -0
  232. package/dist/providers/index.js.map +1 -0
  233. package/dist/providers/lemon-squeezy.d.ts +16 -0
  234. package/dist/providers/lemon-squeezy.d.ts.map +1 -0
  235. package/dist/providers/lemon-squeezy.js +34 -0
  236. package/dist/providers/lemon-squeezy.js.map +1 -0
  237. package/dist/providers/linear.d.ts +37 -0
  238. package/dist/providers/linear.d.ts.map +1 -0
  239. package/dist/providers/linear.js +56 -0
  240. package/dist/providers/linear.js.map +1 -0
  241. package/dist/providers/paddle.d.ts +15 -0
  242. package/dist/providers/paddle.d.ts.map +1 -0
  243. package/dist/providers/paddle.js +103 -0
  244. package/dist/providers/paddle.js.map +1 -0
  245. package/dist/providers/polar.d.ts +26 -0
  246. package/dist/providers/polar.d.ts.map +1 -0
  247. package/dist/providers/polar.js +78 -0
  248. package/dist/providers/polar.js.map +1 -0
  249. package/dist/providers/razorpay.d.ts +20 -0
  250. package/dist/providers/razorpay.d.ts.map +1 -0
  251. package/dist/providers/razorpay.js +41 -0
  252. package/dist/providers/razorpay.js.map +1 -0
  253. package/dist/providers/replicate.d.ts +20 -0
  254. package/dist/providers/replicate.d.ts.map +1 -0
  255. package/dist/providers/replicate.js +23 -0
  256. package/dist/providers/replicate.js.map +1 -0
  257. package/dist/providers/sanity.d.ts +39 -0
  258. package/dist/providers/sanity.d.ts.map +1 -0
  259. package/dist/providers/sanity.js +159 -0
  260. package/dist/providers/sanity.js.map +1 -0
  261. package/dist/providers/sentry.d.ts +26 -0
  262. package/dist/providers/sentry.d.ts.map +1 -0
  263. package/dist/providers/sentry.js +45 -0
  264. package/dist/providers/sentry.js.map +1 -0
  265. package/dist/providers/shared.d.ts +15 -0
  266. package/dist/providers/shared.d.ts.map +1 -0
  267. package/dist/providers/shared.js +40 -0
  268. package/dist/providers/shared.js.map +1 -0
  269. package/dist/providers/shopify.d.ts +9 -0
  270. package/dist/providers/shopify.d.ts.map +1 -0
  271. package/dist/providers/shopify.js +38 -0
  272. package/dist/providers/shopify.js.map +1 -0
  273. package/dist/providers/standard-webhooks-protocol.d.ts +15 -0
  274. package/dist/providers/standard-webhooks-protocol.d.ts.map +1 -0
  275. package/dist/providers/standard-webhooks-protocol.js +95 -0
  276. package/dist/providers/standard-webhooks-protocol.js.map +1 -0
  277. package/dist/providers/standard-webhooks.d.ts +10 -0
  278. package/dist/providers/standard-webhooks.d.ts.map +1 -0
  279. package/dist/providers/standard-webhooks.js +13 -0
  280. package/dist/providers/standard-webhooks.js.map +1 -0
  281. package/dist/providers/stripe.d.ts +8 -0
  282. package/dist/providers/stripe.d.ts.map +1 -0
  283. package/dist/providers/stripe.js +79 -0
  284. package/dist/providers/stripe.js.map +1 -0
  285. package/dist/providers/svix.d.ts +10 -0
  286. package/dist/providers/svix.d.ts.map +1 -0
  287. package/dist/providers/svix.js +61 -0
  288. package/dist/providers/svix.js.map +1 -0
  289. package/dist/providers/vercel.d.ts +16 -0
  290. package/dist/providers/vercel.d.ts.map +1 -0
  291. package/dist/providers/vercel.js +37 -0
  292. package/dist/providers/vercel.js.map +1 -0
  293. package/dist/providers/workos.d.ts +33 -0
  294. package/dist/providers/workos.d.ts.map +1 -0
  295. package/dist/providers/workos.js +118 -0
  296. package/dist/providers/workos.js.map +1 -0
  297. package/dist/types.d.ts +69 -0
  298. package/dist/types.d.ts.map +1 -0
  299. package/dist/types.js +2 -0
  300. package/dist/types.js.map +1 -0
  301. 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
- # Temporary Holding Version
1
+ # Hooklatch
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Verify webhooks. Preserve every byte.**
4
+
5
+ [![CI](https://github.com/Its-amanjha/Hooklatch/actions/workflows/ci.yml/badge.svg)](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"}