// logs in terminal
verifySignature {
digest: "72bd23aa53d2534f4c3c7dc2ed14bbfa5784bc51b4d5e357d6db81401687e253",
signatureHeader: "05f2c5e80f9a44cf1aa554aa18f82ba4202d9ead7fa4cc4b954b126525fcd9b8",
}
Signing secret is invalid
❌ LemonSqueezy Webhook Error message: Signing secret is invalid
Last active
October 23, 2023 03:26
-
-
Save waptik/e2231da2c0f863d47b89b060db8fe49a to your computer and use it in GitHub Desktop.
Immitating nodejs' cryptoHmac+Buffer.from in Deno by reproducing lemonsqueezy's webhook payload signing(https://github.com/amosbastian/template/blob/bdc77731d4f0dfef0bd2529205c8dd5b733d5b4a/apps/web/app/api/lemonsqueezy/route.ts#L76)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @see https://github.com/denoland/deno/blob/main/cli/tests/unit_node/crypto/crypto_hash_test.ts#L17 | |
export * as crypto from 'node:crypto'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Router } from 'oak'; | |
import { createHmac, timingSafeEqual } from 'crypto.ts'; | |
enum LemonSqueezyWebhooks { | |
SubscriptionCreated = 'subscription_created', | |
SubscriptionUpdated = 'subscription_updated', | |
SubscriptionPaymentSuccess = 'subscription_payment_success', | |
OrderCreated = 'order_created', | |
} | |
const lemonsqueezyRouter = new Router(); | |
lemonsqueezyRouter.post('/lemonsqueezy', async (ctx) => { | |
const signatureHeader = ctx.request.headers.get('x-signature'); | |
const eventName = ctx.request.headers.get('x-event-name'); | |
const body = await ctx.request.body({ type: 'text' }).value; | |
console.log(`lemonsqueezy webhook body.json`, body); | |
try { | |
if (!signatureHeader) { | |
console.error(`Signature header not found`); | |
throw new Error('Signature header not found'); | |
} | |
const webhookSecret = Deno.env.get('LEMONSQUEEZY_WEBHOOK_SECRET') ?? ''; | |
const encoder = new TextEncoder(); | |
const hmac = createHmac( | |
'sha256', | |
webhookSecret, | |
); | |
const digestString = hmac.update(payload).digest('hex'); | |
const digest = encoder.encode(digestString); | |
const signature = encoder.encode( | |
signatureHeader, | |
); | |
console.log('LemonSqueezy.verifySignature', { | |
digest, | |
signatureHeader, | |
webhookSecret, | |
digestString | |
}); | |
const result = timingSafeEqual(digest, signature); | |
console.log(`verifySignature`, result); | |
console.log('verifySignature', { | |
digest, | |
signatureHeader, | |
}); | |
const isSigningSecretValid = timingSafeEqual(digest, signature); | |
if (!isSigningSecretValid) { | |
console.error(`Signing secret is invalid`); | |
throw new Error('Signing secret is invalid'); | |
} | |
} catch (err) { | |
console.log(`❌ LemonSqueezy Webhook Error message: ${err.message}`); | |
ctx.response.status = 401; | |
ctx.response.body = `Unauthorized: ${err.message}`; | |
return; | |
} | |
console.info( | |
`[Lemon Squeezy] Received Webhook`, | |
{ | |
type: eventName, | |
}, | |
); | |
switch (body.meta.event_name) { | |
case LemonSqueezyWebhooks.OrderCreated: { | |
// data.data is an Order | |
console.log({ body }); | |
break; | |
} | |
default: | |
break; | |
} | |
ctx.response.body = 'success'; | |
ctx.response.status = 200; | |
}); | |
export default lemonsqueezyRouter; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It fails with lemonsqueezy payload but works with another provider