1
+ import { Webhooks } from "../api/resources/webhooks/client/Client" ;
2
+ import crypto from "crypto" ;
3
+
4
+ // Extends the namespace declared in the Fern generated client
5
+ declare module "../api/resources/webhooks/client/Client" {
6
+ export namespace Webhooks {
7
+ interface RequestSignatureDetails {
8
+ /** The headers of the incoming webhook request as a record-like object */
9
+ headers : Record < string , string > ;
10
+ /** The body of the incoming webhook request as a string */
11
+ body : string ;
12
+ /** The secret key generated when creating the webhook or the OAuth client secret */
13
+ secret : string ;
14
+ }
15
+ }
16
+ }
17
+
18
+ export class Client extends Webhooks {
19
+ constructor ( protected readonly _options : Webhooks . Options ) {
20
+ super ( _options ) ;
21
+ }
22
+
23
+ /**
24
+ * Verify that the signature on the webhook message is from Webflow
25
+ * @link https://developers.webflow.com/data/docs/working-with-webhooks#validating-request-signatures
26
+ *
27
+ * @param {Webhooks.RequestSignatureDetails.headers } requestSignatureDetails - details of the incoming webhook request
28
+ * @example
29
+ * function incomingWebhookRouteHandler(req, res) {
30
+ * const headers = req.headers;
31
+ * const body = JSON.stringify(req.body);
32
+ * const secret = getWebhookSecret(WEBHOOK_ID);
33
+ * const isAuthenticated = await client.webhooks.verifySignature({ headers, body, secret });
34
+ *
35
+ * if (isAuthenticated) {
36
+ * // Process the webhook
37
+ * } else {
38
+ * // Alert the user that the webhook is not authenticated
39
+ * }
40
+ * res.sendStatus(200);
41
+ * }
42
+ *
43
+ */
44
+ public async verifySignature ( { headers, body, secret } : Webhooks . RequestSignatureDetails ) : Promise < boolean > {
45
+ // Creates a HMAC signature following directions from https://developers.webflow.com/data/docs/working-with-webhooks#steps-to-validate-the-request-signature
46
+ const createHmac = async ( signingSecret : string , message : string ) => {
47
+ const encoder = new TextEncoder ( ) ;
48
+
49
+ // Encode the signingSecret key
50
+ // @ts -expect-error TS2339: Property 'subtle' does not exist on type 'typeof import("crypto")'.
51
+ const key = await crypto . subtle . importKey (
52
+ "raw" ,
53
+ encoder . encode ( signingSecret ) ,
54
+ { name : "HMAC" , hash : "SHA-256" } ,
55
+ false ,
56
+ [ "sign" ]
57
+ ) ;
58
+
59
+ // Encode the message and compute HMAC signature
60
+ // @ts -expect-error TS2339: Property 'subtle' does not exist on type 'typeof import("crypto")'.
61
+ const signature = await crypto . subtle . sign ( "HMAC" , key , encoder . encode ( message ) ) ;
62
+
63
+ // Convert signature to hex string
64
+ return Array . from ( new Uint8Array ( signature ) )
65
+ . map ( ( b ) => b . toString ( 16 ) . padStart ( 2 , "0" ) )
66
+ . join ( "" ) ;
67
+ } ;
68
+
69
+ const message = `${ headers [ "x-webflow-timestamp" ] } :${ body } ` ;
70
+
71
+ const generatedSignature = await createHmac ( secret , message ) ;
72
+ return headers [ "x-webflow-signature" ] === generatedSignature ;
73
+ }
74
+ }
0 commit comments