@openwop/openwop 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -21,8 +21,11 @@ export { ACTIVE_RUN_STATUSES, TERMINAL_RUN_STATUSES, isTerminalRunStatus, HTTP_E
21
21
  export type { ActiveRunStatus, TerminalRunStatus, HttpErrorCode, RunErrorCode, RunError, } from './run-helpers.js';
22
22
  export { OPENWOP_COST_ATTRIBUTE_NAMES, sanitizeCostAttributes, } from './cost-attribution.js';
23
23
  export type { OpenwopCostAttributeName } from './cost-attribution.js';
24
+ /** @deprecated Import from `@openwop/openwop/webhooks` — server-only; removed from the barrel in the next major. */
24
25
  export { DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS, verifyWebhookSignature, signWebhookDelivery, } from './webhook-helpers.js';
25
- export type { VerifyWebhookSignatureOptions, VerifyWebhookOutcome, } from './webhook-helpers.js';
26
+ export type { VerifyWebhookSignatureOptions, VerifyWebhookOutcome, SignedWebhookDelivery, } from './webhook-helpers.js';
27
+ export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
28
+ export type { WebhookHeaderRead } from './webhook-header-families.js';
26
29
  export { RegistryClient } from './registry-helpers.js';
27
30
  export type { RegistryClientOptions, RegistryDiscovery, RegistryIndex, RegistryIndexEntry, RegistryPackMetadata, RegistryVersionManifest, } from './registry-helpers.js';
28
31
  export type { A2UISurfacePayload, A2uiSurfaceDeltaFrame, A2uiSurfacePatchOp, AIEnvelope, AIEnvelopeErrorPayload, ClarificationRequestPayload, EnvelopeContract, EnvelopeContractRefusal, EnvelopeContractsCapability, EnvelopeMeta, EnvelopeOutcome, EnvelopeStrictness, PartialInfo, SchemaRequestPayload, SchemaResponsePayload, ValidationDetail, LocalizedContentStatus, LocalizedContentPage, LocalizedContentSection, LocalizedContentPageResponse, LocalizedContentLanguageSettings, PutContentSectionRequest, TriggerSubscriptionRegistration, TriggerSubscription, CreateTriggerSubscriptionResponse, } from './types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -28,7 +28,22 @@ export { OPENWOP_COST_ATTRIBUTE_NAMES, sanitizeCostAttributes, } from './cost-at
28
28
  // HMAC-SHA256 + timestamp freshness window verification per
29
29
  // spec/v1/webhooks.md §"Signature recipe". Receivers MUST verify both
30
30
  // the HMAC AND the timestamp to defeat replay attacks.
31
+ //
32
+ // DEPRECATED ON THE BARREL (openwop-sdks#30). These re-exports are why a
33
+ // browser consumer importing anything at all from `@openwop/openwop` dragged
34
+ // in `node:crypto` and failed the build. Import them from
35
+ // `@openwop/openwop/webhooks` instead; the barrel re-export is retained for
36
+ // compatibility and will be removed in the next major.
37
+ //
38
+ // Until then the `browser` field in package.json substitutes a stub that
39
+ // throws with an explanatory message, so a browser build succeeds and only a
40
+ // browser CALL fails — which is the correct outcome either way, since the
41
+ // subscription secret must never reach a browser.
42
+ /** @deprecated Import from `@openwop/openwop/webhooks` — server-only; removed from the barrel in the next major. */
31
43
  export { DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS, verifyWebhookSignature, signWebhookDelivery, } from './webhook-helpers.js';
44
+ // RFC 0165 §C.3 — browser-safe (no Node builtin): which header family a
45
+ // delivery carries, and the `sha256=` / legacy `v1=` value parser.
46
+ export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
32
47
  // Public-registry read helpers (SDK-5 close-out 2026-05-15). Read-only
33
48
  // typed client for the public node-pack registry at packs.openwop.dev
34
49
  // per spec/v1/registry-operations.md.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA8GtC,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGxC,2EAA2E;AAC3E,6DAA6D;AAC7D,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC;AAM5B,yEAAyE;AACzE,yEAAyE;AACzE,oBAAoB;AACpB,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,cAAc,GACf,MAAM,kBAAkB,CAAC;AAS1B,iDAAiD;AACjD,sEAAsE;AACtE,mEAAmE;AACnE,uDAAuD;AACvD,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAG/B,sEAAsE;AACtE,4DAA4D;AAC5D,sEAAsE;AACtE,uDAAuD;AACvD,OAAO,EACL,wCAAwC,EACxC,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAM9B,uEAAuE;AACvE,sEAAsE;AACtE,sCAAsC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA0CvD,oEAAoE;AACpE,wEAAwE;AACxE,uEAAuE;AACvE,uEAAuE;AACvE,oEAAoE;AACpE,uDAAuD;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAGlE,mEAAmE;AACnE,wEAAwE;AACxE,uEAAuE;AACvE,qEAAqE;AACrE,qEAAqE;AACrE,sEAAsE;AACtE,8DAA8D;AAC9D,sEAAsE;AACtE,oDAAoD;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA8GtC,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGxC,2EAA2E;AAC3E,6DAA6D;AAC7D,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC;AAM5B,yEAAyE;AACzE,yEAAyE;AACzE,oBAAoB;AACpB,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,cAAc,GACf,MAAM,kBAAkB,CAAC;AAS1B,iDAAiD;AACjD,sEAAsE;AACtE,mEAAmE;AACnE,uDAAuD;AACvD,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAG/B,sEAAsE;AACtE,4DAA4D;AAC5D,sEAAsE;AACtE,uDAAuD;AACvD,EAAE;AACF,yEAAyE;AACzE,6EAA6E;AAC7E,0DAA0D;AAC1D,4EAA4E;AAC5E,uDAAuD;AACvD,EAAE;AACF,yEAAyE;AACzE,6EAA6E;AAC7E,0EAA0E;AAC1E,kDAAkD;AAClD,oHAAoH;AACpH,OAAO,EACL,wCAAwC,EACxC,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAM9B,wEAAwE;AACxE,mEAAmE;AACnE,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGhH,uEAAuE;AACvE,sEAAsE;AACtE,sCAAsC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA0CvD,oEAAoE;AACpE,wEAAwE;AACxE,uEAAuE;AACvE,uEAAuE;AACvE,oEAAoE;AACpE,uDAAuD;AACvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAGlE,mEAAmE;AACnE,wEAAwE;AACxE,uEAAuE;AACvE,qEAAqE;AACrE,qEAAqE;AACrE,sEAAsE;AACtE,8DAA8D;AAC9D,sEAAsE;AACtE,oDAAoD;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Webhook header families and signature-value parsing (RFC 0165 §C.3).
3
+ * Pure — no Node builtin — so both the server module (`webhook-helpers.ts`)
4
+ * and the browser stub re-export it. Verification itself stays server-only.
5
+ */
6
+ /** `sha256=<hex>` or `v1=<hex>` → `<hex>`; anything else → null. */
7
+ export declare function parseSignatureValue(value: string): string | null;
8
+ /**
9
+ * Header-name families a delivery may carry, in the order a receiver SHOULD
10
+ * prefer them (RFC 0165 §C.1): the v2-bound `OpenWOP-*` family, the v1
11
+ * canonical `X-openwop-*` family, then the legacy names this SDK used to
12
+ * document. Lookups are case-insensitive.
13
+ */
14
+ export declare const WEBHOOK_HEADER_FAMILIES: ReadonlyArray<{
15
+ readonly signature: string;
16
+ readonly timestamp: string;
17
+ readonly algorithm?: string;
18
+ }>;
19
+ export interface WebhookHeaderRead {
20
+ readonly signatureHeader: string;
21
+ readonly timestampHeader: string;
22
+ /** Which family was read: `openwop`, `x-openwop`, or `legacy`. */
23
+ readonly family: 'openwop' | 'x-openwop' | 'legacy';
24
+ }
25
+ /**
26
+ * Pick the signature + timestamp values out of a delivery's headers, first
27
+ * present family wins. Returns null when no family is complete. Pass a plain
28
+ * object (any casing) or a `Headers`-like with a `get` method.
29
+ */
30
+ export declare function readWebhookHeaders(headers: Record<string, string | string[] | undefined> | {
31
+ get(name: string): string | null;
32
+ }): WebhookHeaderRead | null;
33
+ //# sourceMappingURL=webhook-header-families.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-header-families.d.ts","sourceRoot":"","sources":["../src/webhook-header-families.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQhE;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,aAAa,CAAC;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAI1I,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;CACrD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG;IAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE,GAC5F,iBAAiB,GAAG,IAAI,CAmB1B"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Webhook header families and signature-value parsing (RFC 0165 §C.3).
3
+ * Pure — no Node builtin — so both the server module (`webhook-helpers.ts`)
4
+ * and the browser stub re-export it. Verification itself stays server-only.
5
+ */
6
+ /** Accepted signature-value prefixes, spec form first. */
7
+ const SIGNATURE_VALUE_PREFIXES = ['sha256=', 'v1='];
8
+ /** `sha256=<hex>` or `v1=<hex>` → `<hex>`; anything else → null. */
9
+ export function parseSignatureValue(value) {
10
+ for (const p of SIGNATURE_VALUE_PREFIXES) {
11
+ if (value.startsWith(p)) {
12
+ const hex = value.slice(p.length);
13
+ return /^[0-9a-f]+$/i.test(hex) ? hex : null;
14
+ }
15
+ }
16
+ return null;
17
+ }
18
+ /**
19
+ * Header-name families a delivery may carry, in the order a receiver SHOULD
20
+ * prefer them (RFC 0165 §C.1): the v2-bound `OpenWOP-*` family, the v1
21
+ * canonical `X-openwop-*` family, then the legacy names this SDK used to
22
+ * document. Lookups are case-insensitive.
23
+ */
24
+ export const WEBHOOK_HEADER_FAMILIES = [
25
+ { signature: 'OpenWOP-Signature', timestamp: 'OpenWOP-Timestamp', algorithm: 'OpenWOP-Signature-Algorithm' },
26
+ { signature: 'X-openwop-Signature', timestamp: 'X-openwop-Timestamp', algorithm: 'X-openwop-Signature-Algorithm' },
27
+ { signature: 'openwop-Webhook-Signature', timestamp: 'openwop-Webhook-Timestamp' },
28
+ ];
29
+ /**
30
+ * Pick the signature + timestamp values out of a delivery's headers, first
31
+ * present family wins. Returns null when no family is complete. Pass a plain
32
+ * object (any casing) or a `Headers`-like with a `get` method.
33
+ */
34
+ export function readWebhookHeaders(headers) {
35
+ const get = (name) => {
36
+ if (typeof headers.get === 'function') {
37
+ const v = headers.get(name);
38
+ return v === null ? undefined : v;
39
+ }
40
+ const rec = headers;
41
+ const key = Object.keys(rec).find((k) => k.toLowerCase() === name.toLowerCase());
42
+ const v = key === undefined ? undefined : rec[key];
43
+ return Array.isArray(v) ? v[0] : v;
44
+ };
45
+ const families = ['openwop', 'x-openwop', 'legacy'];
46
+ for (let i = 0; i < WEBHOOK_HEADER_FAMILIES.length; i++) {
47
+ const f = WEBHOOK_HEADER_FAMILIES[i];
48
+ const sig = get(f.signature);
49
+ const ts = get(f.timestamp);
50
+ if (sig !== undefined && ts !== undefined)
51
+ return { signatureHeader: sig, timestampHeader: ts, family: families[i] };
52
+ }
53
+ return null;
54
+ }
55
+ //# sourceMappingURL=webhook-header-families.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-header-families.js","sourceRoot":"","sources":["../src/webhook-header-families.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,0DAA0D;AAC1D,MAAM,wBAAwB,GAAG,CAAC,SAAS,EAAE,KAAK,CAAU,CAAC;AAE7D,oEAAoE;AACpE,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,KAAK,MAAM,CAAC,IAAI,wBAAwB,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAClC,OAAO,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/C,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAA2G;IAC7I,EAAE,SAAS,EAAE,mBAAmB,EAAE,SAAS,EAAE,mBAAmB,EAAE,SAAS,EAAE,6BAA6B,EAAE;IAC5G,EAAE,SAAS,EAAE,qBAAqB,EAAE,SAAS,EAAE,qBAAqB,EAAE,SAAS,EAAE,+BAA+B,EAAE;IAClH,EAAE,SAAS,EAAE,2BAA2B,EAAE,SAAS,EAAE,2BAA2B,EAAE;CACnF,CAAC;AASF;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAA6F;IAE7F,MAAM,GAAG,GAAG,CAAC,IAAY,EAAsB,EAAE;QAC/C,IAAI,OAAQ,OAA6B,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;YAC7D,MAAM,CAAC,GAAI,OAAgD,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACtE,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QACpC,CAAC;QACD,MAAM,GAAG,GAAG,OAAwD,CAAC;QACrE,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QACjF,MAAM,CAAC,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnD,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAU,CAAC;IAC7D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,uBAAuB,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxD,MAAM,CAAC,GAAG,uBAAuB,CAAC,CAAC,CAAE,CAAC;QACtC,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAC7B,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAC5B,IAAI,GAAG,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS;YAAE,OAAO,EAAE,eAAe,EAAE,GAAG,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAE,EAAE,CAAC;IACxH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Browser substitute for `webhook-helpers.ts` (openwop-sdks#30).
3
+ *
4
+ * ## Why this file exists
5
+ *
6
+ * `webhook-helpers.ts` imports `node:crypto` for `createHmac` and
7
+ * `timingSafeEqual`. The package barrel re-exports it, and the `exports` map
8
+ * offers only `"."` — so a browser consumer importing ANYTHING from
9
+ * `@openwop/openwop` pulled the barrel, pulled the webhook helpers, and pulled
10
+ * `node:crypto`. Vite/Rollup then failed the BUILD with
11
+ *
12
+ * "createHmac" is not exported by "__vite-browser-external"
13
+ *
14
+ * which names a bundler-internal shim rather than the real cause, so the error
15
+ * points nowhere useful. Reported 2026-05-26 and still reproducible against the
16
+ * published 1.7.0 fifteen months later.
17
+ *
18
+ * The `browser` field in package.json maps the Node module to this one, so the
19
+ * barrel is importable in a browser again. Webhook signature verification is a
20
+ * SERVER concern — a browser has no business holding the subscription secret —
21
+ * so the honest browser behaviour is to keep the import working and refuse the
22
+ * call, not to ship a second crypto implementation.
23
+ *
24
+ * ## Why it throws rather than returning a failure
25
+ *
26
+ * `verifyWebhookSignature` returning `{ ok: false }` in a browser would be a
27
+ * silent security downgrade: a caller that treats "not ok" as "reject the
28
+ * delivery" behaves identically whether the signature was forged or the
29
+ * platform simply could not check it. Those are different facts and only one of
30
+ * them is about the payload. Throwing keeps them distinguishable.
31
+ */
32
+ /** @see spec/v1/webhooks.md §"Replay attack protection" */
33
+ export declare const DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS = 300;
34
+ export declare function verifyWebhookSignature(): never;
35
+ export declare function signWebhookDelivery(): never;
36
+ export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
37
+ export type { VerifyWebhookSignatureOptions, VerifyWebhookOutcome, SignedWebhookDelivery, WebhookHeaderRead } from './webhook-helpers.js';
38
+ //# sourceMappingURL=webhook-helpers.browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-helpers.browser.d.ts","sourceRoot":"","sources":["../src/webhook-helpers.browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAOH,2DAA2D;AAC3D,eAAO,MAAM,wCAAwC,MAAM,CAAC;AAE5D,wBAAgB,sBAAsB,IAAI,KAAK,CAE9C;AAED,wBAAgB,mBAAmB,IAAI,KAAK,CAE3C;AAKD,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEhH,YAAY,EAAE,6BAA6B,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Browser substitute for `webhook-helpers.ts` (openwop-sdks#30).
3
+ *
4
+ * ## Why this file exists
5
+ *
6
+ * `webhook-helpers.ts` imports `node:crypto` for `createHmac` and
7
+ * `timingSafeEqual`. The package barrel re-exports it, and the `exports` map
8
+ * offers only `"."` — so a browser consumer importing ANYTHING from
9
+ * `@openwop/openwop` pulled the barrel, pulled the webhook helpers, and pulled
10
+ * `node:crypto`. Vite/Rollup then failed the BUILD with
11
+ *
12
+ * "createHmac" is not exported by "__vite-browser-external"
13
+ *
14
+ * which names a bundler-internal shim rather than the real cause, so the error
15
+ * points nowhere useful. Reported 2026-05-26 and still reproducible against the
16
+ * published 1.7.0 fifteen months later.
17
+ *
18
+ * The `browser` field in package.json maps the Node module to this one, so the
19
+ * barrel is importable in a browser again. Webhook signature verification is a
20
+ * SERVER concern — a browser has no business holding the subscription secret —
21
+ * so the honest browser behaviour is to keep the import working and refuse the
22
+ * call, not to ship a second crypto implementation.
23
+ *
24
+ * ## Why it throws rather than returning a failure
25
+ *
26
+ * `verifyWebhookSignature` returning `{ ok: false }` in a browser would be a
27
+ * silent security downgrade: a caller that treats "not ok" as "reject the
28
+ * delivery" behaves identically whether the signature was forged or the
29
+ * platform simply could not check it. Those are different facts and only one of
30
+ * them is about the payload. Throwing keeps them distinguishable.
31
+ */
32
+ const REASON = 'openwop: webhook signature helpers require Node\'s crypto (HMAC-SHA256 + timingSafeEqual) and are not available in a browser build. '
33
+ + 'Webhook verification is a server-side concern — the subscription secret must never reach a browser. '
34
+ + 'Import them on the server from "@openwop/openwop/webhooks".';
35
+ /** @see spec/v1/webhooks.md §"Replay attack protection" */
36
+ export const DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS = 300;
37
+ export function verifyWebhookSignature() {
38
+ throw new Error(REASON);
39
+ }
40
+ export function signWebhookDelivery() {
41
+ throw new Error(REASON);
42
+ }
43
+ // RFC 0165 §C.3 — the header-family readers need no Node builtin, so the
44
+ // browser build carries the real implementations (a browser MAY inspect which
45
+ // family a delivery carries; it still MUST NOT verify — no secret in a browser).
46
+ export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
47
+ //# sourceMappingURL=webhook-helpers.browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-helpers.browser.js","sourceRoot":"","sources":["../src/webhook-helpers.browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,MAAM,GACV,sIAAsI;MACpI,sGAAsG;MACtG,6DAA6D,CAAC;AAElE,2DAA2D;AAC3D,MAAM,CAAC,MAAM,wCAAwC,GAAG,GAAG,CAAC;AAE5D,MAAM,UAAU,sBAAsB;IACpC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;AAC1B,CAAC;AAED,yEAAyE;AACzE,8EAA8E;AAC9E,iFAAiF;AACjF,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC"}
@@ -4,15 +4,23 @@
4
4
  * timestamp freshness before accepting a delivery — verifying HMAC
5
5
  * alone leaves the receiver open to replay attacks.
6
6
  *
7
- * The canonical signing recipe:
7
+ * The canonical signing recipe (`webhooks.md` §"Headers"):
8
8
  *
9
9
  * hmac = HMAC-SHA256(secret, `${timestamp}.${rawBody}`)
10
- * header `openwop-Webhook-Signature: v1=<hmac-hex>`
11
- * header `openwop-Webhook-Timestamp: <unix-seconds>`
10
+ * header `X-openwop-Signature: sha256=<hmac-hex>` (v1 canonical)
11
+ * header `OpenWOP-Signature: sha256=<hmac-hex>` (RFC 0165 §C.1, dual-emitted)
12
+ * header `X-openwop-Timestamp` / `OpenWOP-Timestamp: <unix-seconds>`
13
+ *
14
+ * History (RFC 0165 §C.3): until 1.9.0 this helper read a header named
15
+ * `openwop-Webhook-Signature` carrying `v1=<hex>` — a name and value shape
16
+ * that appear in no spec file. A spec-conformant `sha256=` delivery failed
17
+ * verification outright. The helper now accepts BOTH value forms, and
18
+ * `readWebhookHeaders` picks the first present header family in spec order
19
+ * (`OpenWOP-*`, then `X-openwop-*`, then the legacy `openwop-Webhook-*`).
12
20
  *
13
21
  * Verification:
14
22
  *
15
- * 1. Parse the `v1=<hex>` value from the signature header.
23
+ * 1. Parse the `sha256=<hex>` (or legacy `v1=<hex>`) value from the signature header.
16
24
  * 2. Recompute `expected = HMAC-SHA256(secret, `${timestamp}.${rawBody}`)`.
17
25
  * 3. Compare using **constant-time** equality (timing-safe).
18
26
  * 4. Reject when `|now - timestamp|` exceeds the freshness window
@@ -55,19 +63,27 @@ export type VerifyWebhookOutcome = {
55
63
  * signs the exact bytes it delivered.
56
64
  *
57
65
  * @param secret The pre-shared secret returned from `webhooks.register`.
58
- * @param signatureHeader The value of the `openwop-Webhook-Signature` header (e.g., `"v1=abc123…"`).
59
- * @param timestampHeader The value of the `openwop-Webhook-Timestamp` header (unix seconds as string).
66
+ * @param signatureHeader The value of the signature header — `OpenWOP-Signature` / `X-openwop-Signature` (`"sha256=abc123…"`) or the legacy `openwop-Webhook-Signature` (`"v1=abc123…"`); see `readWebhookHeaders`.
67
+ * @param timestampHeader The value of the matching timestamp header (unix seconds as string).
60
68
  * @param rawBody The exact request body bytes the host POSTed.
61
69
  */
62
70
  export declare function verifyWebhookSignature(secret: string, signatureHeader: string, timestampHeader: string, rawBody: string | Buffer, options?: VerifyWebhookSignatureOptions): VerifyWebhookOutcome;
71
+ export interface SignedWebhookDelivery {
72
+ /** Spec form: `sha256=<hex>` (webhooks.md §"Headers"). */
73
+ readonly signatureHeader: string;
74
+ /** Legacy form this SDK used to emit: `v1=<hex>` (RFC 0165 §C.3). */
75
+ readonly legacySignatureHeader: string;
76
+ readonly timestampHeader: string;
77
+ /** Every header a host should send during the RFC 0165 overlap, by exact name. */
78
+ readonly headers: Readonly<Record<string, string>>;
79
+ }
63
80
  /**
64
81
  * Compute the canonical webhook signature for a payload — useful when
65
82
  * implementing a host (forward direction) OR when generating test
66
83
  * fixtures. Receivers verify via `verifyWebhookSignature`; this is the
67
84
  * inverse.
68
85
  */
69
- export declare function signWebhookDelivery(secret: string, timestamp: number, rawBody: string | Buffer): {
70
- signatureHeader: string;
71
- timestampHeader: string;
72
- };
86
+ export declare function signWebhookDelivery(secret: string, timestamp: number, rawBody: string | Buffer): SignedWebhookDelivery;
87
+ export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
88
+ export type { WebhookHeaderRead } from './webhook-header-families.js';
73
89
  //# sourceMappingURL=webhook-helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"webhook-helpers.d.ts","sourceRoot":"","sources":["../src/webhook-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,sFAAsF;AACtF,eAAO,MAAM,wCAAwC,MAAM,CAAC;AAE5D,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACf;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,oBAAoB,GAAG,mBAAmB,GAAG,6BAA6B,GAAG,4BAA4B,GAAG,4BAA4B,CAAA;CAAE,CAAC;AAEvK;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,EACvB,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,OAAO,GAAE,6BAAkC,GAC1C,oBAAoB,CAyCtB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GAAG,MAAM,GACvB;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAOtD"}
1
+ {"version":3,"file":"webhook-helpers.d.ts","sourceRoot":"","sources":["../src/webhook-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAKH,sFAAsF;AACtF,eAAO,MAAM,wCAAwC,MAAM,CAAC;AAE5D,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACf;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,oBAAoB,GAAG,mBAAmB,GAAG,6BAA6B,GAAG,4BAA4B,GAAG,4BAA4B,CAAA;CAAE,CAAC;AAEvK;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,EACvB,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,OAAO,GAAE,6BAAkC,GAC1C,oBAAoB,CA0CtB;AAED,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,qEAAqE;IACrE,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACpD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GAAG,MAAM,GACvB,qBAAqB,CAkBvB;AAED,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAChH,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC"}
@@ -4,15 +4,23 @@
4
4
  * timestamp freshness before accepting a delivery — verifying HMAC
5
5
  * alone leaves the receiver open to replay attacks.
6
6
  *
7
- * The canonical signing recipe:
7
+ * The canonical signing recipe (`webhooks.md` §"Headers"):
8
8
  *
9
9
  * hmac = HMAC-SHA256(secret, `${timestamp}.${rawBody}`)
10
- * header `openwop-Webhook-Signature: v1=<hmac-hex>`
11
- * header `openwop-Webhook-Timestamp: <unix-seconds>`
10
+ * header `X-openwop-Signature: sha256=<hmac-hex>` (v1 canonical)
11
+ * header `OpenWOP-Signature: sha256=<hmac-hex>` (RFC 0165 §C.1, dual-emitted)
12
+ * header `X-openwop-Timestamp` / `OpenWOP-Timestamp: <unix-seconds>`
13
+ *
14
+ * History (RFC 0165 §C.3): until 1.9.0 this helper read a header named
15
+ * `openwop-Webhook-Signature` carrying `v1=<hex>` — a name and value shape
16
+ * that appear in no spec file. A spec-conformant `sha256=` delivery failed
17
+ * verification outright. The helper now accepts BOTH value forms, and
18
+ * `readWebhookHeaders` picks the first present header family in spec order
19
+ * (`OpenWOP-*`, then `X-openwop-*`, then the legacy `openwop-Webhook-*`).
12
20
  *
13
21
  * Verification:
14
22
  *
15
- * 1. Parse the `v1=<hex>` value from the signature header.
23
+ * 1. Parse the `sha256=<hex>` (or legacy `v1=<hex>`) value from the signature header.
16
24
  * 2. Recompute `expected = HMAC-SHA256(secret, `${timestamp}.${rawBody}`)`.
17
25
  * 3. Compare using **constant-time** equality (timing-safe).
18
26
  * 4. Reject when `|now - timestamp|` exceeds the freshness window
@@ -25,6 +33,7 @@
25
33
  * @module @openwop/openwop/webhook-helpers
26
34
  */
27
35
  import { createHmac, timingSafeEqual } from 'node:crypto';
36
+ import { parseSignatureValue } from './webhook-header-families.js';
28
37
  /** Default freshness window per `spec/v1/webhooks.md` §"Replay attack resistance". */
29
38
  export const DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS = 300;
30
39
  /**
@@ -37,16 +46,17 @@ export const DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS = 300;
37
46
  * signs the exact bytes it delivered.
38
47
  *
39
48
  * @param secret The pre-shared secret returned from `webhooks.register`.
40
- * @param signatureHeader The value of the `openwop-Webhook-Signature` header (e.g., `"v1=abc123…"`).
41
- * @param timestampHeader The value of the `openwop-Webhook-Timestamp` header (unix seconds as string).
49
+ * @param signatureHeader The value of the signature header — `OpenWOP-Signature` / `X-openwop-Signature` (`"sha256=abc123…"`) or the legacy `openwop-Webhook-Signature` (`"v1=abc123…"`); see `readWebhookHeaders`.
50
+ * @param timestampHeader The value of the matching timestamp header (unix seconds as string).
42
51
  * @param rawBody The exact request body bytes the host POSTed.
43
52
  */
44
53
  export function verifyWebhookSignature(secret, signatureHeader, timestampHeader, rawBody, options = {}) {
45
- // 1. Parse the signature header.
46
- if (!signatureHeader.startsWith('v1=')) {
54
+ // 1. Parse the signature header — spec form `sha256=<hex>` (webhooks.md
55
+ // §"Headers") or the legacy SDK form `v1=<hex>` (RFC 0165 §C.3).
56
+ const providedHex = parseSignatureValue(signatureHeader);
57
+ if (providedHex === null) {
47
58
  return { valid: false, reason: 'malformed_signature_header' };
48
59
  }
49
- const providedHex = signatureHeader.slice(3);
50
60
  if (!/^[0-9a-f]+$/i.test(providedHex)) {
51
61
  return { valid: false, reason: 'malformed_signature_header' };
52
62
  }
@@ -90,8 +100,20 @@ export function signWebhookDelivery(secret, timestamp, rawBody) {
90
100
  const bodyStr = typeof rawBody === 'string' ? rawBody : rawBody.toString('utf8');
91
101
  const hex = createHmac('sha256', secret).update(`${timestamp}.${bodyStr}`, 'utf8').digest('hex');
92
102
  return {
93
- signatureHeader: `v1=${hex}`,
103
+ signatureHeader: `sha256=${hex}`,
104
+ legacySignatureHeader: `v1=${hex}`,
94
105
  timestampHeader: String(timestamp),
106
+ headers: {
107
+ 'OpenWOP-Signature': `sha256=${hex}`,
108
+ 'OpenWOP-Timestamp': String(timestamp),
109
+ 'OpenWOP-Signature-Algorithm': 'v1',
110
+ 'X-openwop-Signature': `sha256=${hex}`,
111
+ 'X-openwop-Timestamp': String(timestamp),
112
+ 'X-openwop-Signature-Algorithm': 'v1',
113
+ 'openwop-Webhook-Signature': `v1=${hex}`,
114
+ 'openwop-Webhook-Timestamp': String(timestamp),
115
+ },
95
116
  };
96
117
  }
118
+ export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
97
119
  //# sourceMappingURL=webhook-helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"webhook-helpers.js","sourceRoot":"","sources":["../src/webhook-helpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"webhook-helpers.js","sourceRoot":"","sources":["../src/webhook-helpers.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "Production-ready TypeScript reference SDK for OpenWOP v1.0 compliant servers.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,8 +14,14 @@
14
14
  "exports": {
15
15
  ".": {
16
16
  "types": "./dist/index.d.ts",
17
+ "browser": "./dist/index.js",
17
18
  "import": "./dist/index.js"
18
- }
19
+ },
20
+ "./webhooks": {
21
+ "types": "./dist/webhook-helpers.d.ts",
22
+ "import": "./dist/webhook-helpers.js"
23
+ },
24
+ "./package.json": "./package.json"
19
25
  },
20
26
  "files": [
21
27
  "dist",
@@ -40,5 +46,8 @@
40
46
  },
41
47
  "overrides": {
42
48
  "esbuild": "^0.25.0"
49
+ },
50
+ "browser": {
51
+ "./dist/webhook-helpers.js": "./dist/webhook-helpers.browser.js"
43
52
  }
44
53
  }
package/src/index.ts CHANGED
@@ -188,6 +188,18 @@ export type { OpenwopCostAttributeName } from './cost-attribution.js';
188
188
  // HMAC-SHA256 + timestamp freshness window verification per
189
189
  // spec/v1/webhooks.md §"Signature recipe". Receivers MUST verify both
190
190
  // the HMAC AND the timestamp to defeat replay attacks.
191
+ //
192
+ // DEPRECATED ON THE BARREL (openwop-sdks#30). These re-exports are why a
193
+ // browser consumer importing anything at all from `@openwop/openwop` dragged
194
+ // in `node:crypto` and failed the build. Import them from
195
+ // `@openwop/openwop/webhooks` instead; the barrel re-export is retained for
196
+ // compatibility and will be removed in the next major.
197
+ //
198
+ // Until then the `browser` field in package.json substitutes a stub that
199
+ // throws with an explanatory message, so a browser build succeeds and only a
200
+ // browser CALL fails — which is the correct outcome either way, since the
201
+ // subscription secret must never reach a browser.
202
+ /** @deprecated Import from `@openwop/openwop/webhooks` — server-only; removed from the barrel in the next major. */
191
203
  export {
192
204
  DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS,
193
205
  verifyWebhookSignature,
@@ -196,7 +208,12 @@ export {
196
208
  export type {
197
209
  VerifyWebhookSignatureOptions,
198
210
  VerifyWebhookOutcome,
211
+ SignedWebhookDelivery,
199
212
  } from './webhook-helpers.js';
213
+ // RFC 0165 §C.3 — browser-safe (no Node builtin): which header family a
214
+ // delivery carries, and the `sha256=` / legacy `v1=` value parser.
215
+ export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
216
+ export type { WebhookHeaderRead } from './webhook-header-families.js';
200
217
 
201
218
  // Public-registry read helpers (SDK-5 close-out 2026-05-15). Read-only
202
219
  // typed client for the public node-pack registry at packs.openwop.dev
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Webhook header families and signature-value parsing (RFC 0165 §C.3).
3
+ * Pure — no Node builtin — so both the server module (`webhook-helpers.ts`)
4
+ * and the browser stub re-export it. Verification itself stays server-only.
5
+ */
6
+
7
+ /** Accepted signature-value prefixes, spec form first. */
8
+ const SIGNATURE_VALUE_PREFIXES = ['sha256=', 'v1='] as const;
9
+
10
+ /** `sha256=<hex>` or `v1=<hex>` → `<hex>`; anything else → null. */
11
+ export function parseSignatureValue(value: string): string | null {
12
+ for (const p of SIGNATURE_VALUE_PREFIXES) {
13
+ if (value.startsWith(p)) {
14
+ const hex = value.slice(p.length);
15
+ return /^[0-9a-f]+$/i.test(hex) ? hex : null;
16
+ }
17
+ }
18
+ return null;
19
+ }
20
+
21
+ /**
22
+ * Header-name families a delivery may carry, in the order a receiver SHOULD
23
+ * prefer them (RFC 0165 §C.1): the v2-bound `OpenWOP-*` family, the v1
24
+ * canonical `X-openwop-*` family, then the legacy names this SDK used to
25
+ * document. Lookups are case-insensitive.
26
+ */
27
+ export const WEBHOOK_HEADER_FAMILIES: ReadonlyArray<{ readonly signature: string; readonly timestamp: string; readonly algorithm?: string }> = [
28
+ { signature: 'OpenWOP-Signature', timestamp: 'OpenWOP-Timestamp', algorithm: 'OpenWOP-Signature-Algorithm' },
29
+ { signature: 'X-openwop-Signature', timestamp: 'X-openwop-Timestamp', algorithm: 'X-openwop-Signature-Algorithm' },
30
+ { signature: 'openwop-Webhook-Signature', timestamp: 'openwop-Webhook-Timestamp' },
31
+ ];
32
+
33
+ export interface WebhookHeaderRead {
34
+ readonly signatureHeader: string;
35
+ readonly timestampHeader: string;
36
+ /** Which family was read: `openwop`, `x-openwop`, or `legacy`. */
37
+ readonly family: 'openwop' | 'x-openwop' | 'legacy';
38
+ }
39
+
40
+ /**
41
+ * Pick the signature + timestamp values out of a delivery's headers, first
42
+ * present family wins. Returns null when no family is complete. Pass a plain
43
+ * object (any casing) or a `Headers`-like with a `get` method.
44
+ */
45
+ export function readWebhookHeaders(
46
+ headers: Record<string, string | string[] | undefined> | { get(name: string): string | null },
47
+ ): WebhookHeaderRead | null {
48
+ const get = (name: string): string | undefined => {
49
+ if (typeof (headers as { get?: unknown }).get === 'function') {
50
+ const v = (headers as { get(name: string): string | null }).get(name);
51
+ return v === null ? undefined : v;
52
+ }
53
+ const rec = headers as Record<string, string | string[] | undefined>;
54
+ const key = Object.keys(rec).find((k) => k.toLowerCase() === name.toLowerCase());
55
+ const v = key === undefined ? undefined : rec[key];
56
+ return Array.isArray(v) ? v[0] : v;
57
+ };
58
+ const families = ['openwop', 'x-openwop', 'legacy'] as const;
59
+ for (let i = 0; i < WEBHOOK_HEADER_FAMILIES.length; i++) {
60
+ const f = WEBHOOK_HEADER_FAMILIES[i]!;
61
+ const sig = get(f.signature);
62
+ const ts = get(f.timestamp);
63
+ if (sig !== undefined && ts !== undefined) return { signatureHeader: sig, timestampHeader: ts, family: families[i]! };
64
+ }
65
+ return null;
66
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Browser substitute for `webhook-helpers.ts` (openwop-sdks#30).
3
+ *
4
+ * ## Why this file exists
5
+ *
6
+ * `webhook-helpers.ts` imports `node:crypto` for `createHmac` and
7
+ * `timingSafeEqual`. The package barrel re-exports it, and the `exports` map
8
+ * offers only `"."` — so a browser consumer importing ANYTHING from
9
+ * `@openwop/openwop` pulled the barrel, pulled the webhook helpers, and pulled
10
+ * `node:crypto`. Vite/Rollup then failed the BUILD with
11
+ *
12
+ * "createHmac" is not exported by "__vite-browser-external"
13
+ *
14
+ * which names a bundler-internal shim rather than the real cause, so the error
15
+ * points nowhere useful. Reported 2026-05-26 and still reproducible against the
16
+ * published 1.7.0 fifteen months later.
17
+ *
18
+ * The `browser` field in package.json maps the Node module to this one, so the
19
+ * barrel is importable in a browser again. Webhook signature verification is a
20
+ * SERVER concern — a browser has no business holding the subscription secret —
21
+ * so the honest browser behaviour is to keep the import working and refuse the
22
+ * call, not to ship a second crypto implementation.
23
+ *
24
+ * ## Why it throws rather than returning a failure
25
+ *
26
+ * `verifyWebhookSignature` returning `{ ok: false }` in a browser would be a
27
+ * silent security downgrade: a caller that treats "not ok" as "reject the
28
+ * delivery" behaves identically whether the signature was forged or the
29
+ * platform simply could not check it. Those are different facts and only one of
30
+ * them is about the payload. Throwing keeps them distinguishable.
31
+ */
32
+
33
+ const REASON =
34
+ 'openwop: webhook signature helpers require Node\'s crypto (HMAC-SHA256 + timingSafeEqual) and are not available in a browser build. '
35
+ + 'Webhook verification is a server-side concern — the subscription secret must never reach a browser. '
36
+ + 'Import them on the server from "@openwop/openwop/webhooks".';
37
+
38
+ /** @see spec/v1/webhooks.md §"Replay attack protection" */
39
+ export const DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS = 300;
40
+
41
+ export function verifyWebhookSignature(): never {
42
+ throw new Error(REASON);
43
+ }
44
+
45
+ export function signWebhookDelivery(): never {
46
+ throw new Error(REASON);
47
+ }
48
+
49
+ // RFC 0165 §C.3 — the header-family readers need no Node builtin, so the
50
+ // browser build carries the real implementations (a browser MAY inspect which
51
+ // family a delivery carries; it still MUST NOT verify — no secret in a browser).
52
+ export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
53
+
54
+ export type { VerifyWebhookSignatureOptions, VerifyWebhookOutcome, SignedWebhookDelivery, WebhookHeaderRead } from './webhook-helpers.js';
@@ -4,15 +4,23 @@
4
4
  * timestamp freshness before accepting a delivery — verifying HMAC
5
5
  * alone leaves the receiver open to replay attacks.
6
6
  *
7
- * The canonical signing recipe:
7
+ * The canonical signing recipe (`webhooks.md` §"Headers"):
8
8
  *
9
9
  * hmac = HMAC-SHA256(secret, `${timestamp}.${rawBody}`)
10
- * header `openwop-Webhook-Signature: v1=<hmac-hex>`
11
- * header `openwop-Webhook-Timestamp: <unix-seconds>`
10
+ * header `X-openwop-Signature: sha256=<hmac-hex>` (v1 canonical)
11
+ * header `OpenWOP-Signature: sha256=<hmac-hex>` (RFC 0165 §C.1, dual-emitted)
12
+ * header `X-openwop-Timestamp` / `OpenWOP-Timestamp: <unix-seconds>`
13
+ *
14
+ * History (RFC 0165 §C.3): until 1.9.0 this helper read a header named
15
+ * `openwop-Webhook-Signature` carrying `v1=<hex>` — a name and value shape
16
+ * that appear in no spec file. A spec-conformant `sha256=` delivery failed
17
+ * verification outright. The helper now accepts BOTH value forms, and
18
+ * `readWebhookHeaders` picks the first present header family in spec order
19
+ * (`OpenWOP-*`, then `X-openwop-*`, then the legacy `openwop-Webhook-*`).
12
20
  *
13
21
  * Verification:
14
22
  *
15
- * 1. Parse the `v1=<hex>` value from the signature header.
23
+ * 1. Parse the `sha256=<hex>` (or legacy `v1=<hex>`) value from the signature header.
16
24
  * 2. Recompute `expected = HMAC-SHA256(secret, `${timestamp}.${rawBody}`)`.
17
25
  * 3. Compare using **constant-time** equality (timing-safe).
18
26
  * 4. Reject when `|now - timestamp|` exceeds the freshness window
@@ -26,6 +34,7 @@
26
34
  */
27
35
 
28
36
  import { createHmac, timingSafeEqual } from 'node:crypto';
37
+ import { parseSignatureValue } from './webhook-header-families.js';
29
38
 
30
39
  /** Default freshness window per `spec/v1/webhooks.md` §"Replay attack resistance". */
31
40
  export const DEFAULT_WEBHOOK_FRESHNESS_WINDOW_SECONDS = 300;
@@ -58,8 +67,8 @@ export type VerifyWebhookOutcome =
58
67
  * signs the exact bytes it delivered.
59
68
  *
60
69
  * @param secret The pre-shared secret returned from `webhooks.register`.
61
- * @param signatureHeader The value of the `openwop-Webhook-Signature` header (e.g., `"v1=abc123…"`).
62
- * @param timestampHeader The value of the `openwop-Webhook-Timestamp` header (unix seconds as string).
70
+ * @param signatureHeader The value of the signature header — `OpenWOP-Signature` / `X-openwop-Signature` (`"sha256=abc123…"`) or the legacy `openwop-Webhook-Signature` (`"v1=abc123…"`); see `readWebhookHeaders`.
71
+ * @param timestampHeader The value of the matching timestamp header (unix seconds as string).
63
72
  * @param rawBody The exact request body bytes the host POSTed.
64
73
  */
65
74
  export function verifyWebhookSignature(
@@ -69,11 +78,12 @@ export function verifyWebhookSignature(
69
78
  rawBody: string | Buffer,
70
79
  options: VerifyWebhookSignatureOptions = {},
71
80
  ): VerifyWebhookOutcome {
72
- // 1. Parse the signature header.
73
- if (!signatureHeader.startsWith('v1=')) {
81
+ // 1. Parse the signature header — spec form `sha256=<hex>` (webhooks.md
82
+ // §"Headers") or the legacy SDK form `v1=<hex>` (RFC 0165 §C.3).
83
+ const providedHex = parseSignatureValue(signatureHeader);
84
+ if (providedHex === null) {
74
85
  return { valid: false, reason: 'malformed_signature_header' };
75
86
  }
76
- const providedHex = signatureHeader.slice(3);
77
87
  if (!/^[0-9a-f]+$/i.test(providedHex)) {
78
88
  return { valid: false, reason: 'malformed_signature_header' };
79
89
  }
@@ -111,6 +121,16 @@ export function verifyWebhookSignature(
111
121
  return { valid: true };
112
122
  }
113
123
 
124
+ export interface SignedWebhookDelivery {
125
+ /** Spec form: `sha256=<hex>` (webhooks.md §"Headers"). */
126
+ readonly signatureHeader: string;
127
+ /** Legacy form this SDK used to emit: `v1=<hex>` (RFC 0165 §C.3). */
128
+ readonly legacySignatureHeader: string;
129
+ readonly timestampHeader: string;
130
+ /** Every header a host should send during the RFC 0165 overlap, by exact name. */
131
+ readonly headers: Readonly<Record<string, string>>;
132
+ }
133
+
114
134
  /**
115
135
  * Compute the canonical webhook signature for a payload — useful when
116
136
  * implementing a host (forward direction) OR when generating test
@@ -121,11 +141,25 @@ export function signWebhookDelivery(
121
141
  secret: string,
122
142
  timestamp: number,
123
143
  rawBody: string | Buffer,
124
- ): { signatureHeader: string; timestampHeader: string } {
144
+ ): SignedWebhookDelivery {
125
145
  const bodyStr = typeof rawBody === 'string' ? rawBody : rawBody.toString('utf8');
126
146
  const hex = createHmac('sha256', secret).update(`${timestamp}.${bodyStr}`, 'utf8').digest('hex');
127
147
  return {
128
- signatureHeader: `v1=${hex}`,
148
+ signatureHeader: `sha256=${hex}`,
149
+ legacySignatureHeader: `v1=${hex}`,
129
150
  timestampHeader: String(timestamp),
151
+ headers: {
152
+ 'OpenWOP-Signature': `sha256=${hex}`,
153
+ 'OpenWOP-Timestamp': String(timestamp),
154
+ 'OpenWOP-Signature-Algorithm': 'v1',
155
+ 'X-openwop-Signature': `sha256=${hex}`,
156
+ 'X-openwop-Timestamp': String(timestamp),
157
+ 'X-openwop-Signature-Algorithm': 'v1',
158
+ 'openwop-Webhook-Signature': `v1=${hex}`,
159
+ 'openwop-Webhook-Timestamp': String(timestamp),
160
+ },
130
161
  };
131
162
  }
163
+
164
+ export { WEBHOOK_HEADER_FAMILIES, parseSignatureValue, readWebhookHeaders } from './webhook-header-families.js';
165
+ export type { WebhookHeaderRead } from './webhook-header-families.js';