-
Notifications
You must be signed in to change notification settings - Fork 36
Description
I'd like to have strong types on our API for webhook requests that come from Lemon Squeezy.
I was surprised to find out that the SDK is only meant to execute requests, not handle incoming ones.
Not only did I have to write (and fix!) the signature verification (the docs example didn't work for me), but I'm having a hard time with matching the types.
Example:
On the subscription_created event, I get the following object:
But the 'Subscription' exported type from the SDK is:
type Subscription = {
jsonapi: {
version: string;
};
links: Pick<Links, "self">;
data: SubscriptionData;
included?: Data<Record<string, unknown>, unknown>[] | undefined;
}But 'SubscriptionData' isn't even an exported type, so I can't import it in my project and use that type to define what my webhook endpoint receives (in addition to the 'meta' object).
As for the verification of signed requests, the docs show the following Node example:
import crypto from "node:crypto";
const secret = 'SIGNING_SECRET';
const hmac = crypto.createHmac('sha256', secret);
const digest = Buffer.from(hmac.update(request.rawBody).digest('hex'), 'utf8');
const signature = Buffer.from(request.get('X-Signature') || '', 'utf8');
if (!crypto.timingSafeEqual(digest, signature)) {
throw new Error('Invalid signature.');
}But I had to change it to the following due to TS errors (ignore the diff. structure):
import crypto from 'node:crypto';
export default function verifySignature(
secret: string,
signature_header: string | undefined,
payload: string,
): boolean {
const hmac = crypto.createHmac('sha256', secret);
const digest = Uint8Array.from(
Buffer.from(hmac.update(payload).digest('hex'), 'utf8'),
);
const signature = Uint8Array.from(Buffer.from(signature_header || '', 'utf8'));
if (
digest.length !== signature.length ||
!crypto.timingSafeEqual(digest, signature)
) {
return false;
}
return true;
}Essentially, Buffer.from needed to be wrapped in an Uint8Array.
{ "meta": { "event_name": "subscription_created", "custom_data": { // some custom data, if any } }, "data": { "type": "subscription", "id": "1", "attributes": { // ... } }