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