Skip to content

Commit 9db450b

Browse files
committed
Add webhook idempotency check
1 parent 68f8648 commit 9db450b

1 file changed

Lines changed: 12 additions & 0 deletions

File tree

apps/license-server/src/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,14 @@ app.post('/webhook/paddle', async (c) => {
182182
return c.json({ error: 'Missing customer_id or transaction ID' }, 400)
183183
}
184184

185+
// Idempotency: skip if this transaction was already processed
186+
const idempotencyKey = `transaction:${purchaseData.transactionId}`
187+
const alreadyProcessed = await c.env.LICENSE_CODES.get(idempotencyKey)
188+
if (alreadyProcessed) {
189+
console.log('Transaction already processed:', purchaseData.transactionId)
190+
return c.json({ status: 'already_processed', transactionId: purchaseData.transactionId })
191+
}
192+
185193
console.log('Processing transaction:', purchaseData.transactionId, 'for customer:', purchaseData.customerId)
186194

187195
// Determine Paddle API config (sandbox vs live based on transaction ID)
@@ -229,6 +237,10 @@ app.post('/webhook/paddle', async (c) => {
229237
kv: c.env.LICENSE_CODES,
230238
})
231239

240+
// Mark transaction as processed (7-day TTL)
241+
const sevenDaysInSeconds = 604_800
242+
await c.env.LICENSE_CODES.put(idempotencyKey, 'processed', { expirationTtl: sevenDaysInSeconds })
243+
232244
console.log('Licenses sent to:', customer.email, 'type:', result.licenseType, 'quantity:', result.quantity)
233245
return c.json({ status: 'ok', email: customer.email, licenseType: result.licenseType, quantity: result.quantity })
234246
})

0 commit comments

Comments
 (0)