Skip to content

Commit 8203a36

Browse files
committed
Fix: Send API key via email instead of webhook response (closes #1)
1 parent 77f25b0 commit 8203a36

1 file changed

Lines changed: 41 additions & 3 deletions

File tree

apps/web/api/webhook.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
typescript
12
import type { VercelRequest, VercelResponse } from '@vercel/node';
23
import { createHash, randomBytes, createHmac, timingSafeEqual } from 'node:crypto';
4+
import { Resend } from 'resend'; // Add this import for Resend
35

46
const SUPABASE_URL = process.env.SUPABASE_URL!;
57
const SUPABASE_KEY = process.env.SUPABASE_SERVICE_KEY!;
68
const POLAR_WEBHOOK_SECRET = process.env.POLAR_WEBHOOK_SECRET!;
9+
const RESEND_API_KEY = process.env.RESEND_API_KEY; // Allow undefined, handle gracefully
10+
11+
// Initialize Resend conditionally to prevent errors if RESEND_API_KEY is missing at startup
12+
const resend = RESEND_API_KEY ? new Resend(RESEND_API_KEY) : null;
713

814
// Credit mapping for Polar products
915
const PRODUCT_CREDITS: Record<string, number> = {
@@ -167,14 +173,45 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
167173
});
168174
} else {
169175
// Generate new key (plaintext only exists in this scope, never returned to caller)
170-
const { hash, prefix } = generateApiKey();
176+
// Capture the plaintext 'key' here so it can be emailed.
177+
const { key, hash, prefix } = generateApiKey();
178+
171179
await supabasePost('api_keys', {
172180
user_id: userId,
173181
key_hash: hash,
174182
key_prefix: prefix,
175183
credits_remaining: creditsToAdd,
176184
});
185+
177186
// TODO: Send API key to user via email (Resend/Postmark) instead of returning it
187+
try {
188+
if (!resend) {
189+
console.warn('RESEND_API_KEY is not set. Skipping API key email for user:', email);
190+
} else {
191+
await resend.emails.send({
192+
from: 'Protoscan Vision <onboarding@protoscan.vision>', // IMPORTANT: Replace with your verified sender email in Resend
193+
to: email, // Use the extracted customer email from the webhook payload
194+
subject: 'Your New API Key for Protoscan Vision',
195+
html: `
196+
<p>Hello,</p>
197+
<p>Thank you for your recent purchase! Here is your brand new API key:</p>
198+
<p><strong><code>${key}</code></strong></p>
199+
<p>Please keep this key safe and secure. For security reasons, this key will only be displayed once.</p>
200+
<p>You can find more details on how to use your API key in our <a href="https://docs.protoscan.vision/api-keys" target="_blank">documentation</a>.</p>
201+
<p>If you have any questions or require assistance, please don't hesitate to contact our support team.</p>
202+
<p>Best regards,<br/>The Protoscan Vision Team</p>
203+
`,
204+
text: `Hello,\n\nThank you for your recent purchase! Here is your brand new API key: ${key}\n\nPlease keep this key safe and secure. For security reasons, this key will only be displayed once.\n\nYou can find more details on how to use your API key in our documentation.\n\nIf you have any questions or require assistance, please don't hesitate to contact our support team.\n\nBest regards,\nThe Protoscan Vision Team`,
205+
});
206+
console.log(`API key successfully emailed to ${email} for user ${userId}`);
207+
}
208+
} catch (emailError) {
209+
console.error(`Failed to send API key email to ${email} for user ${userId}:`, emailError);
210+
// Log the email sending failure. It's often best practice to still return a 200 OK
211+
// to the webhook provider (Polar.sh) to prevent them from retrying the *purchase event*.
212+
// A separate mechanism should be in place for handling failed email deliveries
213+
// (e.g., retry queue, admin alert, manual follow-up).
214+
}
178215
}
179216

180217
// 5. Record payment
@@ -186,11 +223,12 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
186223
product_id: productId,
187224
});
188225

189-
return res.status(200).json({ ok: true, credits_added: creditsToAdd });
226+
// The HTTP response should no longer contain the plaintext API key.
227+
return res.status(200).json({ ok: true, credits_added: creditsToAdd, message: 'API key generation and email delivery initiated.' });
190228
} catch (error) {
191229
const message = error instanceof Error ? error.message : String(error);
192230
// Don't expose internal details — log server-side only
193231
console.error(`Webhook processing error: ${message}`);
194232
return res.status(500).json({ error: 'Webhook processing failed' });
195233
}
196-
}
234+
}

0 commit comments

Comments
 (0)