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
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.linearDescriptor = void 0;
4
+ const shared_js_1 = require("./shared.cjs");
5
+ const SIGNATURE_HEADER = 'linear-signature';
6
+ /**
7
+ * Linear webhook signature authentication: `Linear-Signature` is a single
8
+ * 64-character hex HMAC-SHA256 over the exact visible raw request body, keyed
9
+ * with the literal, untrimmed UTF-8 webhook secret. Missing or trim-blank values
10
+ * are `missing-signature`; prefixes, outer whitespace, commas, lists, non-hex,
11
+ * wrong length and all other nonconforming values fail closed as
12
+ * `malformed-signature`. Either hex case is accepted as Hooklatch receiver
13
+ * policy, not as a claim of exact Linear SDK parity.
14
+ *
15
+ * `Linear-Delivery`, `Linear-Event`, `linear-timestamp` and all alien
16
+ * authentication headers are outside the MAC. They cannot establish freshness or
17
+ * trusted identity. Every identity field is null and `authenticatedFields` is
18
+ * empty: this descriptor intentionally performs body-only authentication, does
19
+ * not parse JSON, and never derives identity or time from body content. Old or
20
+ * future unsigned timestamps therefore cannot change authenticity, and this
21
+ * descriptor does not provide replay protection or business idempotency.
22
+ *
23
+ * The secret is literal UTF-8 with no prefix stripping, Base64 decoding,
24
+ * trimming or inferred length bound. Empty secrets are invalid configuration;
25
+ * whitespace-only and non-ASCII secrets remain literal keys. The exact raw body
26
+ * bytes are returned as signed content, so callers must retain the received
27
+ * bytes rather than reserialize parsed JSON.
28
+ *
29
+ * Sources:
30
+ * - https://linear.app/developers/webhooks
31
+ * - https://linear.app/developers/sdk-webhooks
32
+ * - https://github.com/linear/linear/blob/4d9cdbf0beb1d1da1824deb5ff3cd458c71380c9/packages/sdk/src/webhooks/client.ts
33
+ *
34
+ * No Linear-published signature tuple is claimed; test anchors are synthetic.
35
+ */
36
+ exports.linearDescriptor = {
37
+ provider: 'linear',
38
+ decodeSecret(secret) {
39
+ return (0, shared_js_1.decodeLiteralSecret)(secret, 'Linear');
40
+ },
41
+ parse(headers, rawBody) {
42
+ const raw = headers.get(SIGNATURE_HEADER);
43
+ if (raw === undefined || raw.trim() === '') {
44
+ return (0, shared_js_1.failure)('missing-signature', 'No Linear signature header was provided.');
45
+ }
46
+ const signature = (0, shared_js_1.decodeHexSignature)(raw);
47
+ if (signature === null) {
48
+ return (0, shared_js_1.failure)('malformed-signature', 'The Linear signature header is malformed.');
49
+ }
50
+ const identity = {
51
+ deliveryId: null,
52
+ eventId: null,
53
+ timestamp: null,
54
+ authenticatedFields: [],
55
+ };
56
+ return { ok: true, signatures: [signature], signedContent: rawBody, identity };
57
+ },
58
+ };
@@ -0,0 +1,36 @@
1
+ import type { NormalizedHeaders, ParseResult } from '../internal/provider.cjs';
2
+ /**
3
+ * Linear webhook signature authentication: `Linear-Signature` is a single
4
+ * 64-character hex HMAC-SHA256 over the exact visible raw request body, keyed
5
+ * with the literal, untrimmed UTF-8 webhook secret. Missing or trim-blank values
6
+ * are `missing-signature`; prefixes, outer whitespace, commas, lists, non-hex,
7
+ * wrong length and all other nonconforming values fail closed as
8
+ * `malformed-signature`. Either hex case is accepted as Hooklatch receiver
9
+ * policy, not as a claim of exact Linear SDK parity.
10
+ *
11
+ * `Linear-Delivery`, `Linear-Event`, `linear-timestamp` and all alien
12
+ * authentication headers are outside the MAC. They cannot establish freshness or
13
+ * trusted identity. Every identity field is null and `authenticatedFields` is
14
+ * empty: this descriptor intentionally performs body-only authentication, does
15
+ * not parse JSON, and never derives identity or time from body content. Old or
16
+ * future unsigned timestamps therefore cannot change authenticity, and this
17
+ * descriptor does not provide replay protection or business idempotency.
18
+ *
19
+ * The secret is literal UTF-8 with no prefix stripping, Base64 decoding,
20
+ * trimming or inferred length bound. Empty secrets are invalid configuration;
21
+ * whitespace-only and non-ASCII secrets remain literal keys. The exact raw body
22
+ * bytes are returned as signed content, so callers must retain the received
23
+ * bytes rather than reserialize parsed JSON.
24
+ *
25
+ * Sources:
26
+ * - https://linear.app/developers/webhooks
27
+ * - https://linear.app/developers/sdk-webhooks
28
+ * - https://github.com/linear/linear/blob/4d9cdbf0beb1d1da1824deb5ff3cd458c71380c9/packages/sdk/src/webhooks/client.ts
29
+ *
30
+ * No Linear-published signature tuple is claimed; test anchors are synthetic.
31
+ */
32
+ export declare const linearDescriptor: {
33
+ provider: "linear";
34
+ decodeSecret(secret: string): Uint8Array;
35
+ parse(headers: NormalizedHeaders, rawBody: Uint8Array): ParseResult;
36
+ };
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.paddleDescriptor = void 0;
4
+ const bytes_js_1 = require("../internal/bytes.cjs");
5
+ const shared_js_1 = require("./shared.cjs");
6
+ const SIGNATURE_HEADER = 'paddle-signature';
7
+ const TIMESTAMP_KEY = 'ts';
8
+ const SIGNATURE_KEY = 'h1';
9
+ /**
10
+ * Opaque nonempty printable ASCII unknown-field value: no whitespace, commas,
11
+ * semicolons, or additional equals signs. This is a bounded Hooklatch grammar,
12
+ * not a promise to support an unspecified future signature version.
13
+ */
14
+ const OPAQUE_VALUE = /^[\x21-\x2b\x2d-\x3a\x3c\x3e-\x7e]+$/;
15
+ /**
16
+ * Paddle Billing: `Paddle-Signature: ts=<unix seconds>;h1=<64 hex>`.
17
+ * The signature covers the exact `ts` text, a colon, and the untouched raw
18
+ * body. Repeated `h1` values are a Hooklatch rotation accept-set; every `h1`
19
+ * must be valid. Unknown fields such as `h2` are validated then ignored.
20
+ * Freshness is enforced by core, never here.
21
+ * Source: https://developer.paddle.com/webhooks/about/signature-verification/
22
+ */
23
+ exports.paddleDescriptor = {
24
+ provider: 'paddle',
25
+ decodeSecret(secret) {
26
+ return (0, shared_js_1.decodeLiteralSecret)(secret, 'Paddle');
27
+ },
28
+ parse(headers, rawBody) {
29
+ const raw = headers.get(SIGNATURE_HEADER);
30
+ if (raw === undefined || raw.trim() === '') {
31
+ return (0, shared_js_1.failure)('missing-signature', 'No Paddle signature header was provided.');
32
+ }
33
+ // HTTP comma-combined duplicate header values never form a valid field list.
34
+ if (raw.includes(',')) {
35
+ return (0, shared_js_1.failure)('malformed-signature', 'The Paddle signature header is malformed.');
36
+ }
37
+ const signatures = [];
38
+ let timestampText;
39
+ let timestampCount = 0;
40
+ let unknownSchemes = 0;
41
+ for (const rawSegment of raw.split(';')) {
42
+ // Trim outer segment whitespace only; keys and values stay exact.
43
+ const segment = rawSegment.trim();
44
+ if (segment === '') {
45
+ return (0, shared_js_1.failure)('malformed-signature', 'The Paddle signature header is malformed.');
46
+ }
47
+ // Reject missing keys, empty values, and extra equals signs.
48
+ const separator = segment.indexOf('=');
49
+ if (separator <= 0 || separator === segment.length - 1 || segment.indexOf('=', separator + 1) !== -1) {
50
+ return (0, shared_js_1.failure)('malformed-signature', 'The Paddle signature header is malformed.');
51
+ }
52
+ const key = segment.slice(0, separator);
53
+ const value = segment.slice(separator + 1);
54
+ if (!(0, shared_js_1.isSchemeToken)(key)) {
55
+ return (0, shared_js_1.failure)('malformed-signature', 'The Paddle signature header is malformed.');
56
+ }
57
+ if (key === TIMESTAMP_KEY) {
58
+ timestampCount += 1;
59
+ if (timestampCount > 1) {
60
+ return (0, shared_js_1.failure)('malformed-signature', 'The Paddle signature header is malformed.');
61
+ }
62
+ if (!(0, shared_js_1.isCanonicalTimestamp)(value)) {
63
+ return (0, shared_js_1.failure)('malformed-signature', 'The Paddle signature header is malformed.');
64
+ }
65
+ timestampText = value;
66
+ continue;
67
+ }
68
+ if (key === SIGNATURE_KEY) {
69
+ const decoded = (0, shared_js_1.decodeHexSignature)(value);
70
+ // One malformed h1 poisons the whole header, even beside a valid h1.
71
+ if (decoded === null) {
72
+ return (0, shared_js_1.failure)('malformed-signature', 'The Paddle signature header is malformed.');
73
+ }
74
+ signatures.push(decoded);
75
+ continue;
76
+ }
77
+ // Unknown fields are structurally validated, then ignored.
78
+ if (!OPAQUE_VALUE.test(value)) {
79
+ return (0, shared_js_1.failure)('malformed-signature', 'The Paddle signature header is malformed.');
80
+ }
81
+ unknownSchemes += 1;
82
+ }
83
+ // Required-field checks run only after all structure/token validation.
84
+ if (signatures.length === 0) {
85
+ return unknownSchemes > 0
86
+ ? (0, shared_js_1.failure)('unsupported-scheme', 'No supported Paddle signature scheme was provided.')
87
+ : (0, shared_js_1.failure)('missing-signature', 'No Paddle signature value was provided.');
88
+ }
89
+ if (timestampText === undefined) {
90
+ return (0, shared_js_1.failure)('missing-timestamp', 'No Paddle timestamp was provided.');
91
+ }
92
+ const identity = {
93
+ deliveryId: null,
94
+ eventId: null,
95
+ timestamp: Number(timestampText),
96
+ authenticatedFields: ['timestamp'],
97
+ };
98
+ return {
99
+ ok: true,
100
+ signatures,
101
+ signedContent: (0, bytes_js_1.concatBytes)((0, bytes_js_1.utf8)(`${timestampText}:`), rawBody),
102
+ identity,
103
+ };
104
+ },
105
+ };
@@ -0,0 +1,14 @@
1
+ import type { NormalizedHeaders, ParseResult } from '../internal/provider.cjs';
2
+ /**
3
+ * Paddle Billing: `Paddle-Signature: ts=<unix seconds>;h1=<64 hex>`.
4
+ * The signature covers the exact `ts` text, a colon, and the untouched raw
5
+ * body. Repeated `h1` values are a Hooklatch rotation accept-set; every `h1`
6
+ * must be valid. Unknown fields such as `h2` are validated then ignored.
7
+ * Freshness is enforced by core, never here.
8
+ * Source: https://developer.paddle.com/webhooks/about/signature-verification/
9
+ */
10
+ export declare const paddleDescriptor: {
11
+ provider: "paddle";
12
+ decodeSecret(secret: string): Uint8Array;
13
+ parse(headers: NormalizedHeaders, rawBody: Uint8Array): ParseResult;
14
+ };
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createPolarDescriptor = createPolarDescriptor;
4
+ const errors_js_1 = require("../errors.cjs");
5
+ const shared_js_1 = require("./shared.cjs");
6
+ const standard_webhooks_protocol_js_1 = require("./standard-webhooks-protocol.cjs");
7
+ const CANONICAL_HEADERS = {
8
+ id: 'webhook-id',
9
+ timestamp: 'webhook-timestamp',
10
+ signature: 'webhook-signature',
11
+ };
12
+ const currentDescriptor = (0, standard_webhooks_protocol_js_1.createStandardWebhooksProtocol)('polar', 'Polar', CANONICAL_HEADERS);
13
+ const legacyDescriptor = {
14
+ provider: 'polar',
15
+ decodeSecret(secret) {
16
+ return (0, shared_js_1.decodeLiteralSecret)(secret, 'Polar');
17
+ },
18
+ parse: currentDescriptor.parse,
19
+ };
20
+ function invalidOptions() {
21
+ throw new errors_js_1.HooklatchError('invalid-options', 'Invalid Polar provider options.');
22
+ }
23
+ function isConfigObject(value) {
24
+ if (value === null || typeof value !== 'object')
25
+ return false;
26
+ try {
27
+ return !Array.isArray(value);
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
33
+ /**
34
+ * Read the selected mode as an own field and snapshot it before returning the
35
+ * descriptor. Inherited fields are not configuration; an own `secretMode:
36
+ * undefined` is invalid rather than an omitted optional field.
37
+ */
38
+ function snapshotSecretMode(options) {
39
+ if (options === undefined)
40
+ return 'current';
41
+ if (!isConfigObject(options))
42
+ invalidOptions();
43
+ let keys = [];
44
+ let hasMode = false;
45
+ let mode = undefined;
46
+ try {
47
+ keys = Reflect.ownKeys(options).filter((key) => Object.getOwnPropertyDescriptor(options, key)?.enumerable);
48
+ hasMode = Object.hasOwn(options, 'secretMode');
49
+ mode = hasMode ? options.secretMode : undefined;
50
+ }
51
+ catch {
52
+ invalidOptions();
53
+ }
54
+ if (keys.some((key) => key !== 'secretMode'))
55
+ invalidOptions();
56
+ if (!hasMode)
57
+ return 'current';
58
+ if (mode !== 'current' && mode !== 'legacy')
59
+ invalidOptions();
60
+ return mode;
61
+ }
62
+ /**
63
+ * Polar webhooks use the canonical Standard Webhooks headers and signed
64
+ * content: `${webhook-id}.${webhook-timestamp}.${rawBody}`.
65
+ *
66
+ * `current` (the default) keeps the shared Standard Webhooks decoder: an
67
+ * optional `whsec_` prefix is removed and the remainder must decode as
68
+ * canonical Base64 to 24..64 bytes. `legacy` treats the complete secret string
69
+ * as literal UTF-8 bytes, including any `whsec_` prefix, with only the shared
70
+ * nonempty policy. Exactly one mode applies to every secret configured for the
71
+ * descriptor; there is no prefix inference or cross-mode fallback.
72
+ *
73
+ * Polar's SDK tries both key interpretations and floors fractional timestamps.
74
+ * Hooklatch deliberately does neither, and does not decode opaque bodies as
75
+ * text. Source:
76
+ * https://raw.githubusercontent.com/polarsource/polar/89917ad4fe752b06ff84bd31c313d2a53a6085f1/sdk/typescript/src/webhooks.ts
77
+ */
78
+ function createPolarDescriptor(options) {
79
+ return snapshotSecretMode(options) === 'current' ? currentDescriptor : legacyDescriptor;
80
+ }
@@ -0,0 +1,25 @@
1
+ import type { ProviderDescriptor } from '../internal/provider.cjs';
2
+ export interface PolarDescriptorOptions {
3
+ readonly secretMode?: 'current' | 'legacy';
4
+ }
5
+ type PolarDescriptor = {
6
+ readonly provider: 'polar';
7
+ } & Pick<ProviderDescriptor, 'decodeSecret' | 'parse'>;
8
+ /**
9
+ * Polar webhooks use the canonical Standard Webhooks headers and signed
10
+ * content: `${webhook-id}.${webhook-timestamp}.${rawBody}`.
11
+ *
12
+ * `current` (the default) keeps the shared Standard Webhooks decoder: an
13
+ * optional `whsec_` prefix is removed and the remainder must decode as
14
+ * canonical Base64 to 24..64 bytes. `legacy` treats the complete secret string
15
+ * as literal UTF-8 bytes, including any `whsec_` prefix, with only the shared
16
+ * nonempty policy. Exactly one mode applies to every secret configured for the
17
+ * descriptor; there is no prefix inference or cross-mode fallback.
18
+ *
19
+ * Polar's SDK tries both key interpretations and floors fractional timestamps.
20
+ * Hooklatch deliberately does neither, and does not decode opaque bodies as
21
+ * text. Source:
22
+ * https://raw.githubusercontent.com/polarsource/polar/89917ad4fe752b06ff84bd31c313d2a53a6085f1/sdk/typescript/src/webhooks.ts
23
+ */
24
+ export declare function createPolarDescriptor(options?: PolarDescriptorOptions): PolarDescriptor;
25
+ export {};
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.razorpayDescriptor = void 0;
4
+ const shared_js_1 = require("./shared.cjs");
5
+ const SIGNATURE_HEADER = 'x-razorpay-signature';
6
+ const EVENT_ID_HEADER = 'x-razorpay-event-id';
7
+ /**
8
+ * Razorpay `X-Razorpay-Signature`: a single hex HMAC-SHA256 over the raw request
9
+ * body only, keyed with the literal dashboard webhook secret. A missing,
10
+ * blank, prefixed, non-hex, wrong-length or comma-combined value fails closed.
11
+ * The optional `x-razorpay-event-id` is unsigned metadata and never proves
12
+ * authenticity.
13
+ *
14
+ * Sources:
15
+ * - https://razorpay.com/docs/webhooks/validate-test/
16
+ * - https://github.com/razorpay/razorpay-node/blob/b9e8527225a7322ba2671a3bc0b848fd019d2d8c/lib/utils/razorpay-utils.js
17
+ *
18
+ * No Razorpay-published signature vector is claimed; test anchors are synthetic.
19
+ */
20
+ exports.razorpayDescriptor = {
21
+ provider: 'razorpay',
22
+ decodeSecret(secret) {
23
+ return (0, shared_js_1.decodeLiteralSecret)(secret, 'Razorpay');
24
+ },
25
+ parse(headers, rawBody) {
26
+ const raw = headers.get(SIGNATURE_HEADER);
27
+ if (raw === undefined || raw.trim() === '') {
28
+ return (0, shared_js_1.failure)('missing-signature', 'No Razorpay signature header was provided.');
29
+ }
30
+ const signature = (0, shared_js_1.decodeHexSignature)(raw);
31
+ if (signature === null) {
32
+ return (0, shared_js_1.failure)('malformed-signature', 'The Razorpay signature header is malformed.');
33
+ }
34
+ const eventId = headers.get(EVENT_ID_HEADER);
35
+ const identity = {
36
+ deliveryId: null,
37
+ eventId: eventId === undefined || eventId.trim() === '' ? null : eventId,
38
+ timestamp: null,
39
+ authenticatedFields: [],
40
+ };
41
+ return { ok: true, signatures: [signature], signedContent: rawBody, identity };
42
+ },
43
+ };
@@ -0,0 +1,19 @@
1
+ import type { NormalizedHeaders, ParseResult } from '../internal/provider.cjs';
2
+ /**
3
+ * Razorpay `X-Razorpay-Signature`: a single hex HMAC-SHA256 over the raw request
4
+ * body only, keyed with the literal dashboard webhook secret. A missing,
5
+ * blank, prefixed, non-hex, wrong-length or comma-combined value fails closed.
6
+ * The optional `x-razorpay-event-id` is unsigned metadata and never proves
7
+ * authenticity.
8
+ *
9
+ * Sources:
10
+ * - https://razorpay.com/docs/webhooks/validate-test/
11
+ * - https://github.com/razorpay/razorpay-node/blob/b9e8527225a7322ba2671a3bc0b848fd019d2d8c/lib/utils/razorpay-utils.js
12
+ *
13
+ * No Razorpay-published signature vector is claimed; test anchors are synthetic.
14
+ */
15
+ export declare const razorpayDescriptor: {
16
+ provider: "razorpay";
17
+ decodeSecret(secret: string): Uint8Array;
18
+ parse(headers: NormalizedHeaders, rawBody: Uint8Array): ParseResult;
19
+ };
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.replicateDescriptor = void 0;
4
+ const standard_webhooks_protocol_js_1 = require("./standard-webhooks-protocol.cjs");
5
+ /**
6
+ * Replicate webhooks use the canonical Standard Webhooks `webhook-id`,
7
+ * `webhook-timestamp` and `webhook-signature` headers. The secret is
8
+ * Base64-decoded (an optional `whsec_` prefix is stripped) and must decode to
9
+ * 24-64 bytes; the signature covers the exact
10
+ * `${webhook-id}.${webhook-timestamp}.` bytes followed by the raw body.
11
+ *
12
+ * These headers authenticate protocol bytes, not a unique Replicate identity.
13
+ * A signature minted by another canonical alias verifies here when the same
14
+ * secret is configured, so use endpoint-specific secrets. No prediction or
15
+ * event payload is parsed and no resource id is inferred.
16
+ *
17
+ * No Replicate-published signature vector is claimed; the direct tests use
18
+ * synthetic anchors. Source:
19
+ * https://replicate.com/docs/topics/webhooks/verify-webhook
20
+ */
21
+ exports.replicateDescriptor = (0, standard_webhooks_protocol_js_1.createStandardWebhooksProtocol)('replicate', 'Replicate', {
22
+ id: 'webhook-id',
23
+ timestamp: 'webhook-timestamp',
24
+ signature: 'webhook-signature',
25
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Replicate webhooks use the canonical Standard Webhooks `webhook-id`,
3
+ * `webhook-timestamp` and `webhook-signature` headers. The secret is
4
+ * Base64-decoded (an optional `whsec_` prefix is stripped) and must decode to
5
+ * 24-64 bytes; the signature covers the exact
6
+ * `${webhook-id}.${webhook-timestamp}.` bytes followed by the raw body.
7
+ *
8
+ * These headers authenticate protocol bytes, not a unique Replicate identity.
9
+ * A signature minted by another canonical alias verifies here when the same
10
+ * secret is configured, so use endpoint-specific secrets. No prediction or
11
+ * event payload is parsed and no resource id is inferred.
12
+ *
13
+ * No Replicate-published signature vector is claimed; the direct tests use
14
+ * synthetic anchors. Source:
15
+ * https://replicate.com/docs/topics/webhooks/verify-webhook
16
+ */
17
+ export declare const replicateDescriptor: {
18
+ readonly provider: "replicate";
19
+ } & Pick<import("../internal/provider.cjs").ProviderDescriptor, "decodeSecret" | "parse">;
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sanityDescriptor = void 0;
4
+ const bytes_js_1 = require("../internal/bytes.cjs");
5
+ const shared_js_1 = require("./shared.cjs");
6
+ const SIGNATURE_HEADER = 'sanity-webhook-signature';
7
+ const TIMESTAMP_FIELD = 't=';
8
+ const SIGNATURE_FIELD = 'v1=';
9
+ const DIGEST_CHARACTERS = 43;
10
+ const BASE64URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
11
+ /**
12
+ * Sanity does not send signed payloads before 2021-01-01T00:00:00.000Z, so a
13
+ * millisecond timestamp below this bound is not a Sanity delivery. The bound
14
+ * mirrors the reference toolkit; the failure code is Hooklatch policy.
15
+ */
16
+ const MINIMUM_TIMESTAMP_MS = 1609459200000;
17
+ /**
18
+ * Split the exact accepted header grammar `t=<ms>,v1=<digest>`.
19
+ *
20
+ * The fields are ordered, appear exactly once, and carry no whitespace, extra
21
+ * fields, duplicate fields, semicolon separators, or alternate versions. Any
22
+ * other structure is rejected instead of being repaired.
23
+ */
24
+ function splitHeader(raw) {
25
+ const separator = raw.indexOf(',');
26
+ if (separator < 0)
27
+ return null;
28
+ const timestampField = raw.slice(0, separator);
29
+ const signatureField = raw.slice(separator + 1);
30
+ if (!timestampField.startsWith(TIMESTAMP_FIELD))
31
+ return null;
32
+ if (!signatureField.startsWith(SIGNATURE_FIELD))
33
+ return null;
34
+ const timestampText = timestampField.slice(TIMESTAMP_FIELD.length);
35
+ const digest = signatureField.slice(SIGNATURE_FIELD.length);
36
+ if (timestampText.length === 0)
37
+ return null;
38
+ return { timestampText, digest };
39
+ }
40
+ /**
41
+ * Encode the 32 decoded digest bytes as canonical unpadded Base64url.
42
+ *
43
+ * 32 bytes are 256 bits: 42 characters carry 252 of them and the last four
44
+ * bits close the 43rd character.
45
+ */
46
+ function encodeBase64Url(bytes) {
47
+ let output = '';
48
+ let buffer = 0;
49
+ let bits = 0;
50
+ for (const byte of bytes) {
51
+ buffer = (buffer << 8) | byte;
52
+ bits += 8;
53
+ while (bits >= 6) {
54
+ bits -= 6;
55
+ output += BASE64URL_ALPHABET.charAt((buffer >>> bits) & 0x3f);
56
+ buffer &= (1 << bits) - 1;
57
+ }
58
+ }
59
+ return output + BASE64URL_ALPHABET.charAt((buffer << (6 - bits)) & 0x3f);
60
+ }
61
+ /**
62
+ * Decode exactly 43 canonical unpadded Base64url characters into 32 bytes.
63
+ *
64
+ * The 43 characters carry 258 bits, so 32 bytes consume 256 of them and the
65
+ * two remaining low bits of the final character are pad bits. Re-encoding the
66
+ * decoded bytes must reproduce the input exactly, which rejects a digest whose
67
+ * pad bits are not zero.
68
+ */
69
+ function decodeBase64UrlDigest(value) {
70
+ if (value.length !== DIGEST_CHARACTERS)
71
+ return null;
72
+ const bytes = new Uint8Array(32);
73
+ let buffer = 0;
74
+ let bits = 0;
75
+ let offset = 0;
76
+ for (let index = 0; index < value.length; index += 1) {
77
+ const digit = BASE64URL_ALPHABET.indexOf(value.charAt(index));
78
+ if (digit < 0)
79
+ return null;
80
+ buffer = (buffer << 6) | digit;
81
+ bits += 6;
82
+ if (bits < 8)
83
+ continue;
84
+ bits -= 8;
85
+ bytes[offset] = (buffer >>> bits) & 0xff;
86
+ offset += 1;
87
+ buffer &= (1 << bits) - 1;
88
+ }
89
+ return encodeBase64Url(bytes) === value ? bytes : null;
90
+ }
91
+ /**
92
+ * Sanity: `sanity-webhook-signature: t=<unix milliseconds>,v1=<base64url>`.
93
+ *
94
+ * The HMAC-SHA256 covers UTF8(the original `t` text + ".") concatenated with
95
+ * the exact raw body. The secret is the literal, untrimmed UTF-8 text; it is
96
+ * never Base64-decoded. The digest is canonical unpadded Base64url for exactly
97
+ * 32 bytes, decoded by a descriptor-local decoder so the shared standard
98
+ * Base64 handling stays unchanged.
99
+ *
100
+ * The accepted header is exactly `t=<ms>,v1=<digest>`: ordered fields, one
101
+ * each, no whitespace anywhere or outer trimming, no extra or duplicate
102
+ * fields, no semicolon separators, no padding, and no alternate versions. A
103
+ * missing or blank header is `missing-signature`; every other nonmatching
104
+ * structure is `malformed-signature`, with no alternate reason precedence.
105
+ *
106
+ * `t` must be a canonical nonnegative safe decimal integer of milliseconds
107
+ * with no leading zeros, at or after 2021-01-01T00:00:00.000Z. Identity is
108
+ * `timestamp = Number(t) / 1000` with no delivery or event id; freshness is
109
+ * enforced by core and is never evaluated here.
110
+ *
111
+ * The reference SDK verifies UTF-8 text and refuses to sign an empty payload.
112
+ * This descriptor accepts empty, NUL, invalid UTF-8, and non-JSON bodies as
113
+ * opaque bytes, so it extends rather than matches that behavior, and it does
114
+ * not claim exact SDK parity or live interoperability. The strict grammar
115
+ * is checked at parse time. The reference decoder regex accepts spaces and
116
+ * trimming, but full SDK verification compares the original header to canonical
117
+ * re-encoding and also rejects these noncanonical header variants.
118
+ *
119
+ * Sources:
120
+ * - https://www.sanity.io/docs/content-lake/webhook-best-practices
121
+ * - https://github.com/sanity-io/webhook-toolkit/blob/e0c211ed6cd80465a70829e335c9e219f37839ba/src/signature.ts
122
+ */
123
+ exports.sanityDescriptor = {
124
+ provider: 'sanity',
125
+ decodeSecret(secret) {
126
+ return (0, shared_js_1.decodeLiteralSecret)(secret, 'Sanity');
127
+ },
128
+ parse(headers, rawBody) {
129
+ const raw = headers.get(SIGNATURE_HEADER);
130
+ if (raw === undefined || raw.trim() === '') {
131
+ return (0, shared_js_1.failure)('missing-signature', 'No Sanity signature header was provided.');
132
+ }
133
+ const fields = splitHeader(raw);
134
+ if (fields === null) {
135
+ return (0, shared_js_1.failure)('malformed-signature', 'The Sanity signature header is malformed.');
136
+ }
137
+ if (!(0, shared_js_1.isCanonicalTimestamp)(fields.timestampText)) {
138
+ return (0, shared_js_1.failure)('malformed-signature', 'The Sanity signature header is malformed.');
139
+ }
140
+ const timestamp = Number(fields.timestampText);
141
+ if (timestamp < MINIMUM_TIMESTAMP_MS) {
142
+ return (0, shared_js_1.failure)('malformed-signature', 'The Sanity signature header is malformed.');
143
+ }
144
+ const digest = decodeBase64UrlDigest(fields.digest);
145
+ if (digest === null) {
146
+ return (0, shared_js_1.failure)('malformed-signature', 'The Sanity signature header is malformed.');
147
+ }
148
+ const identity = {
149
+ deliveryId: null,
150
+ eventId: null,
151
+ timestamp: timestamp / 1000,
152
+ authenticatedFields: ['timestamp'],
153
+ };
154
+ return {
155
+ ok: true,
156
+ signatures: [digest],
157
+ signedContent: (0, bytes_js_1.concatBytes)((0, bytes_js_1.utf8)(`${fields.timestampText}.`), rawBody),
158
+ identity,
159
+ };
160
+ },
161
+ };
@@ -0,0 +1,38 @@
1
+ import type { NormalizedHeaders, ParseResult } from '../internal/provider.cjs';
2
+ /**
3
+ * Sanity: `sanity-webhook-signature: t=<unix milliseconds>,v1=<base64url>`.
4
+ *
5
+ * The HMAC-SHA256 covers UTF8(the original `t` text + ".") concatenated with
6
+ * the exact raw body. The secret is the literal, untrimmed UTF-8 text; it is
7
+ * never Base64-decoded. The digest is canonical unpadded Base64url for exactly
8
+ * 32 bytes, decoded by a descriptor-local decoder so the shared standard
9
+ * Base64 handling stays unchanged.
10
+ *
11
+ * The accepted header is exactly `t=<ms>,v1=<digest>`: ordered fields, one
12
+ * each, no whitespace anywhere or outer trimming, no extra or duplicate
13
+ * fields, no semicolon separators, no padding, and no alternate versions. A
14
+ * missing or blank header is `missing-signature`; every other nonmatching
15
+ * structure is `malformed-signature`, with no alternate reason precedence.
16
+ *
17
+ * `t` must be a canonical nonnegative safe decimal integer of milliseconds
18
+ * with no leading zeros, at or after 2021-01-01T00:00:00.000Z. Identity is
19
+ * `timestamp = Number(t) / 1000` with no delivery or event id; freshness is
20
+ * enforced by core and is never evaluated here.
21
+ *
22
+ * The reference SDK verifies UTF-8 text and refuses to sign an empty payload.
23
+ * This descriptor accepts empty, NUL, invalid UTF-8, and non-JSON bodies as
24
+ * opaque bytes, so it extends rather than matches that behavior, and it does
25
+ * not claim exact SDK parity or live interoperability. The strict grammar
26
+ * is checked at parse time. The reference decoder regex accepts spaces and
27
+ * trimming, but full SDK verification compares the original header to canonical
28
+ * re-encoding and also rejects these noncanonical header variants.
29
+ *
30
+ * Sources:
31
+ * - https://www.sanity.io/docs/content-lake/webhook-best-practices
32
+ * - https://github.com/sanity-io/webhook-toolkit/blob/e0c211ed6cd80465a70829e335c9e219f37839ba/src/signature.ts
33
+ */
34
+ export declare const sanityDescriptor: {
35
+ provider: "sanity";
36
+ decodeSecret(secret: string): Uint8Array;
37
+ parse(headers: NormalizedHeaders, rawBody: Uint8Array): ParseResult;
38
+ };