@phala/pay 0.2.0 → 0.3.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/CHANGELOG.md CHANGED
@@ -6,6 +6,25 @@ All notable changes to `@phala/pay` are recorded here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.0] - 2026-10-01
10
+
11
+ ### Changed (breaking)
12
+
13
+ - `constructEvent` takes webhook public keys only in Standard Webhooks' `whpk_` form (`whpk_` and
14
+ the standard base64 of the key), as `GET /v1/attestation` lists them; a hex or bare base64 key
15
+ is refused.
16
+ - `parseClientQuote` requires `confirmations`, an integer or `null`, as the service always sends
17
+ it.
18
+ - `constructEvent` requires the event's `actor` and `request` (`null` or the causing request), as
19
+ the service always sends them, and refuses an envelope without them; `WebhookEvent.actor` and
20
+ `request` are no longer optional.
21
+
22
+ ### Fixed
23
+
24
+ - `constructEvent` returns the event's `request` (the causing API request's `id` and
25
+ `idempotency_key`, or `null`), as `WebhookEvent` declares; it was dropped. A malformed `request`
26
+ is refused like any malformed event.
27
+
9
28
  ## [0.2.0] - 2026-09-29
10
29
 
11
30
  ### Changed (breaking)
@@ -133,7 +152,8 @@ All notable changes to `@phala/pay` are recorded here. The format follows
133
152
  without trailing zeros (`1,273.9185`); `tokenAmount(quote)` is the plain decimal a wallet
134
153
  accepts, which `<Checkout>`'s "Exact amount" copies.
135
154
 
136
- [unreleased]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.2.0...HEAD
155
+ [Unreleased]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.3.0...HEAD
156
+ [0.3.0]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.2.0...sdk-js-v0.3.0
137
157
  [0.2.0]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.1.2...sdk-js-v0.2.0
138
158
  [0.1.2]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.1.1...sdk-js-v0.1.2
139
159
  [0.1.1]: https://github.com/Phala-Network/phala-pay/compare/sdk-js-v0.1.0...sdk-js-v0.1.1
package/README.md CHANGED
@@ -19,34 +19,34 @@ connects a wallet with wagmi, RainbowKit, ConnectKit, or AppKit, pass it in inst
19
19
 
20
20
  ## Quickstart
21
21
 
22
- 1. Your backend creates a quote (`POST /v1/quotes` with its secret key, for example
22
+ 1. Your backend creates a quote (`POST /v1/quotes` with its API key, for example
23
23
  `phala-pay`'s `pay.quotes.create`), recomputes its address from the pinned forwarder (the
24
24
  Python SDK does it on every call), and returns only its `client_secret` and that address to
25
25
  the signed-in user's browser.
26
26
  2. Render the checkout with them:
27
27
 
28
- ```tsx
29
- "use client";
30
- import { Checkout } from "@phala/pay/react";
31
-
32
- export function TopUp(props: {
33
- clientSecret: string;
34
- expectedAddress: string;
35
- onPaid: () => void;
36
- onRetry: () => void;
37
- }) {
38
- return (
39
- <Checkout
40
- clientSecret={props.clientSecret}
41
- expectedAddress={props.expectedAddress}
42
- apiBase="https://pay.example.com"
43
- onSuccess={props.onPaid}
44
- onExpire={props.onRetry}
45
- buttonText="Pay with crypto"
46
- />
47
- );
48
- }
49
- ```
28
+ ```tsx
29
+ "use client";
30
+ import { Checkout } from "@phala/pay/react";
31
+
32
+ export function TopUp(props: {
33
+ clientSecret: string;
34
+ expectedAddress: string;
35
+ onPaid: () => void;
36
+ onRetry: () => void;
37
+ }) {
38
+ return (
39
+ <Checkout
40
+ clientSecret={props.clientSecret}
41
+ expectedAddress={props.expectedAddress}
42
+ apiBase="https://pay.example.com"
43
+ onSuccess={props.onPaid}
44
+ onExpire={props.onRetry}
45
+ buttonText="Pay with crypto"
46
+ />
47
+ );
48
+ }
49
+ ```
50
50
 
51
51
  3. Credit the account when your webhook endpoint receives `deposit.credited`. `onSuccess` is for the
52
52
  UI only: the browser is not a trusted source of payment.
@@ -102,12 +102,12 @@ ConnectKit, and AppKit, that is `useWalletClient()`:
102
102
  import { Checkout } from "@phala/pay/react";
103
103
  import { useWalletClient } from "wagmi";
104
104
 
105
- export function TopUp({ clientSecret }: { clientSecret: string }) {
105
+ export function TopUp(props: { clientSecret: string; expectedAddress: string }) {
106
106
  const { data: walletClient } = useWalletClient();
107
107
  return (
108
108
  <Checkout
109
- clientSecret={clientSecret}
110
- expectedAddress={expectedAddress}
109
+ clientSecret={props.clientSecret}
110
+ expectedAddress={props.expectedAddress}
111
111
  apiBase="https://pay.example.com"
112
112
  walletClient={walletClient}
113
113
  />
@@ -121,7 +121,7 @@ as usual. The QR code and manual tabs do not change.
121
121
  ### A customer's deposit address
122
122
 
123
123
  For top-ups of any amount, your backend creates the customer's persistent deposit address with
124
- `POST /v1/deposit_addresses` (secret key) and passes its `address` and `networks` to the page:
124
+ `POST /v1/deposit_addresses` (its API key) and passes its `address` and `networks` to the page:
125
125
  one address for all supported tokens and networks. `<DepositAddress>` lets the payer pick a
126
126
  network and a token and shows a QR code of that token's transfer request, the token contract, and
127
127
  the address to copy, with "send only supported tokens" and "credited at the market rate when it
@@ -232,6 +232,7 @@ const expectedAddress = verifyQuoteAddress(pins, quote); // pass it to <Checkout
232
232
 
233
233
  // Standard Webhooks v1a (ed25519, WebCrypto: Node 20+, Deno, Bun, edge runtimes). Fails closed
234
234
  // unless the signature verifies with a pinned key and the event is your account's in this mode.
235
+ // WEBHOOK_PUBLIC_KEYS: your account's keys in this mode, `whpk_…`, pinned from GET /v1/attestation.
235
236
  const event = await constructEvent(rawBody, request.headers, WEBHOOK_PUBLIC_KEYS, {
236
237
  expectedAccount: "acct_…",
237
238
  expectedLivemode: false,
package/dist/quote.js CHANGED
@@ -31,7 +31,7 @@ export function parseClientQuote(value) {
31
31
  typeof v["payment_uri"] !== "string" ||
32
32
  !isSafeInteger(v["expires_at"]) ||
33
33
  !oneOf(v["payment_status"], PAYMENT_STATUSES) ||
34
- !(confirmations === null || confirmations === undefined || isSafeInteger(confirmations))) {
34
+ !(confirmations === null || isSafeInteger(confirmations))) {
35
35
  throw new TypeError("quote response does not match the public quote view");
36
36
  }
37
37
  return {
@@ -49,7 +49,7 @@ export function parseClientQuote(value) {
49
49
  payment_uri: v["payment_uri"],
50
50
  expires_at: v["expires_at"],
51
51
  payment_status: v["payment_status"],
52
- confirmations: confirmations ?? null,
52
+ confirmations,
53
53
  };
54
54
  }
55
55
  function oneOf(value, allowed) {
package/dist/quote.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"quote.js","sourceRoot":"","sources":["../src/quote.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"quote.js","sourceRoot":"","sources":["../src/quote.ts"],"names":[],"mappings":"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"}
@@ -20,12 +20,12 @@ export interface WebhookEvent {
20
20
  type: string;
21
21
  created: number;
22
22
  /** Who caused it: an API key id (`key_…`), `admin`, or `system`. */
23
- actor?: string;
23
+ actor: string;
24
24
  /**
25
25
  * The API request that caused it, with the `Idempotency-Key` it sent; `null` when the
26
26
  * service's own workers did (a payment credited, a quote expired).
27
27
  */
28
- request?: {
28
+ request: {
29
29
  id: string;
30
30
  idempotency_key: string | null;
31
31
  } | null;
@@ -50,9 +50,10 @@ export interface ConstructEventOptions {
50
50
  }
51
51
  /**
52
52
  * Verifies a delivery and returns its event, as Stripe's `constructEvent` does, failing closed
53
- * unless a `v1a` signature verifies with one of `publicKeys` (hex or base64 raw ed25519 keys; pass
54
- * both while a rotation overlaps), the timestamp is within tolerance, the body's id is the
55
- * `webhook-id`, and the event is `expectedAccount`'s in `expectedLivemode`.
53
+ * unless a `v1a` signature verifies with one of `publicKeys` (ed25519 keys in Standard Webhooks'
54
+ * `whpk_` form, as `GET /v1/attestation` lists them; pass both while a rotation overlaps), the
55
+ * timestamp is within tolerance, the body's id is the `webhook-id`, and the event is
56
+ * `expectedAccount`'s in `expectedLivemode`.
56
57
  *
57
58
  * `payload` is the raw request body, before any JSON parsing.
58
59
  */
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/server/webhook.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,mEAAmE;AACnE,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,SAAkB,IAAI,2BAA2B;CAClD;AAED,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAChE;;;OAGG;IACH,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;CAC1F;AAED,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,eAAe,EAAE,MAAM,CAAC;IACxB,sFAAsF;IACtF,gBAAgB,EAAE,OAAO,CAAC;IAC1B,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,GAAG,UAAU,EAC5B,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,EAChE,UAAU,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EACtC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,YAAY,CAAC,CA8FvB"}
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/server/webhook.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,mEAAmE;AACnE,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,SAAkB,IAAI,2BAA2B;CAClD;AAED,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/D;;;OAGG;IACH,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;CAC1F;AAED,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,eAAe,EAAE,MAAM,CAAC;IACxB,sFAAsF;IACtF,gBAAgB,EAAE,OAAO,CAAC;IAC1B,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,GAAG,UAAU,EAC5B,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,EAChE,UAAU,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EACtC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAgGvB"}
@@ -10,9 +10,10 @@ export class WebhookSignatureError extends Error {
10
10
  }
11
11
  /**
12
12
  * Verifies a delivery and returns its event, as Stripe's `constructEvent` does, failing closed
13
- * unless a `v1a` signature verifies with one of `publicKeys` (hex or base64 raw ed25519 keys; pass
14
- * both while a rotation overlaps), the timestamp is within tolerance, the body's id is the
15
- * `webhook-id`, and the event is `expectedAccount`'s in `expectedLivemode`.
13
+ * unless a `v1a` signature verifies with one of `publicKeys` (ed25519 keys in Standard Webhooks'
14
+ * `whpk_` form, as `GET /v1/attestation` lists them; pass both while a rotation overlaps), the
15
+ * timestamp is within tolerance, the body's id is the `webhook-id`, and the event is
16
+ * `expectedAccount`'s in `expectedLivemode`.
16
17
  *
17
18
  * `payload` is the raw request body, before any JSON parsing.
18
19
  */
@@ -68,7 +69,7 @@ export async function constructEvent(payload, headers, publicKeys, options) {
68
69
  const e = event;
69
70
  const data = e["data"];
70
71
  const object = typeof data === "object" && data !== null ? data["object"] : null;
71
- const { id: eventId, type, account, livemode, created, actor } = e;
72
+ const { id: eventId, type, account, livemode, created, actor, request } = e;
72
73
  if (e["object"] !== "event" ||
73
74
  typeof eventId !== "string" ||
74
75
  typeof type !== "string" ||
@@ -78,7 +79,8 @@ export async function constructEvent(payload, headers, publicKeys, options) {
78
79
  !Number.isSafeInteger(created) ||
79
80
  typeof object !== "object" ||
80
81
  object === null ||
81
- !(actor === undefined || typeof actor === "string")) {
82
+ typeof actor !== "string" ||
83
+ !(request === null || isRequest(request))) {
82
84
  throw new TypeError("webhook body is not an event");
83
85
  }
84
86
  if (eventId !== id) {
@@ -98,7 +100,8 @@ export async function constructEvent(payload, headers, publicKeys, options) {
98
100
  livemode,
99
101
  type,
100
102
  created,
101
- ...(actor === undefined ? {} : { actor }),
103
+ actor,
104
+ request,
102
105
  data: {
103
106
  object: object,
104
107
  ...(typeof previous === "object" && previous !== null
@@ -118,21 +121,20 @@ function header(headers, name) {
118
121
  }
119
122
  return undefined;
120
123
  }
124
+ function isRequest(value) {
125
+ if (typeof value !== "object" || value === null) {
126
+ return false;
127
+ }
128
+ const { id, idempotency_key: key } = value;
129
+ return typeof id === "string" && (key === null || typeof key === "string");
130
+ }
121
131
  async function importKey(encoded) {
122
- const value = encoded.trim().replace(/^0x/, "");
123
- const raw = /^[0-9a-fA-F]{64}$/.test(value) ? hex(value) : base64(value);
132
+ const raw = encoded.startsWith("whpk_") ? base64(encoded.slice("whpk_".length)) : undefined;
124
133
  if (raw?.length !== 32) {
125
- throw new TypeError("a webhook public key is 32 bytes of hex or base64");
134
+ throw new TypeError("a webhook public key is whpk_ and the base64 of 32 bytes");
126
135
  }
127
136
  return crypto.subtle.importKey("raw", raw, "Ed25519", false, ["verify"]);
128
137
  }
129
- function hex(value) {
130
- const bytes = new Uint8Array(value.length / 2);
131
- for (let index = 0; index < bytes.length; index += 1) {
132
- bytes[index] = Number.parseInt(value.slice(index * 2, index * 2 + 2), 16);
133
- }
134
- return bytes;
135
- }
136
138
  function base64(value) {
137
139
  if (!/^[A-Za-z0-9+/]*={0,2}$/.test(value) || value.length % 4 !== 0) {
138
140
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/server/webhook.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/server/webhook.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phala/pay",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Phala Pay checkout: a framework-agnostic client and React bindings",
5
5
  "keywords": [
6
6
  "crypto",
package/src/quote.ts CHANGED
@@ -70,7 +70,7 @@ export function parseClientQuote(value: unknown): ClientQuote {
70
70
  typeof v["payment_uri"] !== "string" ||
71
71
  !isSafeInteger(v["expires_at"]) ||
72
72
  !oneOf(v["payment_status"], PAYMENT_STATUSES) ||
73
- !(confirmations === null || confirmations === undefined || isSafeInteger(confirmations))
73
+ !(confirmations === null || isSafeInteger(confirmations))
74
74
  ) {
75
75
  throw new TypeError("quote response does not match the public quote view");
76
76
  }
@@ -89,7 +89,7 @@ export function parseClientQuote(value: unknown): ClientQuote {
89
89
  payment_uri: v["payment_uri"],
90
90
  expires_at: v["expires_at"],
91
91
  payment_status: v["payment_status"],
92
- confirmations: confirmations ?? null,
92
+ confirmations,
93
93
  };
94
94
  }
95
95
 
@@ -22,12 +22,12 @@ export interface WebhookEvent {
22
22
  type: string;
23
23
  created: number;
24
24
  /** Who caused it: an API key id (`key_…`), `admin`, or `system`. */
25
- actor?: string;
25
+ actor: string;
26
26
  /**
27
27
  * The API request that caused it, with the `Idempotency-Key` it sent; `null` when the
28
28
  * service's own workers did (a payment credited, a quote expired).
29
29
  */
30
- request?: { id: string; idempotency_key: string | null } | null;
30
+ request: { id: string; idempotency_key: string | null } | null;
31
31
  /**
32
32
  * `object` is the object as it was when the event happened, never re-rendered; `*.updated`
33
33
  * events add `previous_attributes`, the former values of what changed.
@@ -48,9 +48,10 @@ export interface ConstructEventOptions {
48
48
 
49
49
  /**
50
50
  * Verifies a delivery and returns its event, as Stripe's `constructEvent` does, failing closed
51
- * unless a `v1a` signature verifies with one of `publicKeys` (hex or base64 raw ed25519 keys; pass
52
- * both while a rotation overlaps), the timestamp is within tolerance, the body's id is the
53
- * `webhook-id`, and the event is `expectedAccount`'s in `expectedLivemode`.
51
+ * unless a `v1a` signature verifies with one of `publicKeys` (ed25519 keys in Standard Webhooks'
52
+ * `whpk_` form, as `GET /v1/attestation` lists them; pass both while a rotation overlaps), the
53
+ * timestamp is within tolerance, the body's id is the `webhook-id`, and the event is
54
+ * `expectedAccount`'s in `expectedLivemode`.
54
55
  *
55
56
  * `payload` is the raw request body, before any JSON parsing.
56
57
  */
@@ -113,7 +114,7 @@ export async function constructEvent(
113
114
  const data = e["data"];
114
115
  const object: unknown =
115
116
  typeof data === "object" && data !== null ? (data as Record<string, unknown>)["object"] : null;
116
- const { id: eventId, type, account, livemode, created, actor } = e;
117
+ const { id: eventId, type, account, livemode, created, actor, request } = e;
117
118
  if (
118
119
  e["object"] !== "event" ||
119
120
  typeof eventId !== "string" ||
@@ -124,7 +125,8 @@ export async function constructEvent(
124
125
  !Number.isSafeInteger(created) ||
125
126
  typeof object !== "object" ||
126
127
  object === null ||
127
- !(actor === undefined || typeof actor === "string")
128
+ typeof actor !== "string" ||
129
+ !(request === null || isRequest(request))
128
130
  ) {
129
131
  throw new TypeError("webhook body is not an event");
130
132
  }
@@ -145,7 +147,8 @@ export async function constructEvent(
145
147
  livemode,
146
148
  type,
147
149
  created,
148
- ...(actor === undefined ? {} : { actor }),
150
+ actor,
151
+ request,
149
152
  data: {
150
153
  object: object as Record<string, unknown>,
151
154
  ...(typeof previous === "object" && previous !== null
@@ -170,21 +173,20 @@ function header(
170
173
  return undefined;
171
174
  }
172
175
 
173
- async function importKey(encoded: string): Promise<CryptoKey> {
174
- const value = encoded.trim().replace(/^0x/, "");
175
- const raw = /^[0-9a-fA-F]{64}$/.test(value) ? hex(value) : base64(value);
176
- if (raw?.length !== 32) {
177
- throw new TypeError("a webhook public key is 32 bytes of hex or base64");
176
+ function isRequest(value: unknown): value is { id: string; idempotency_key: string | null } {
177
+ if (typeof value !== "object" || value === null) {
178
+ return false;
178
179
  }
179
- return crypto.subtle.importKey("raw", raw, "Ed25519", false, ["verify"]);
180
+ const { id, idempotency_key: key } = value as Record<string, unknown>;
181
+ return typeof id === "string" && (key === null || typeof key === "string");
180
182
  }
181
183
 
182
- function hex(value: string): Uint8Array<ArrayBuffer> {
183
- const bytes = new Uint8Array(value.length / 2);
184
- for (let index = 0; index < bytes.length; index += 1) {
185
- bytes[index] = Number.parseInt(value.slice(index * 2, index * 2 + 2), 16);
184
+ async function importKey(encoded: string): Promise<CryptoKey> {
185
+ const raw = encoded.startsWith("whpk_") ? base64(encoded.slice("whpk_".length)) : undefined;
186
+ if (raw?.length !== 32) {
187
+ throw new TypeError("a webhook public key is whpk_ and the base64 of 32 bytes");
186
188
  }
187
- return bytes;
189
+ return crypto.subtle.importKey("raw", raw, "Ed25519", false, ["verify"]);
188
190
  }
189
191
 
190
192
  function base64(value: string): Uint8Array<ArrayBuffer> | undefined {