Webhook and API Key Implementation Guide
Generated: 2025-07-23 20:30 UTC
Status: Implementation Guide
Verified: Based on deployment documentation
Overview
This guide details how to implement the Stripe webhook handler for automatic API key generation and email delivery when users subscribe to either the Free or Pro tier.
Architecture
graph TD
A[User Clicks Subscribe] --> B[Stripe Checkout]
B --> C[Payment Processed]
C --> D[Webhook Event]
D --> E[stripe-webhook Function]
E --> F[Generate API Key]
E --> G[Store in Firestore]
E --> H[Send Email]
F --> I[User Receives API Key]
Webhook Handler Implementation
1. Function Structure
Create functions/stripe-webhook/index.js
:
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const { Firestore } = require('@google-cloud/firestore');
const crypto = require('crypto');
const { sendWelcomeEmail } = require('../lib/email');
const db = new Firestore();
exports.stripeWebhook = async (req, res) => {
// Verify webhook signature
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.rawBody,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
console.error('Webhook signature verification failed:', err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
switch (event.type) {
case 'checkout.session.completed':
await handleCheckoutComplete(event.data.object);
break;
case 'customer.subscription.updated':
await handleSubscriptionUpdate(event.data.object);
break;
case 'customer.subscription.deleted':
await handleSubscriptionCanceled(event.data.object);
break;
default:
console.log(`Unhandled event type ${event.type}`);
}
res.json({ received: true });
};
2. API Key Generation
function generateApiKey() {
// Format: ctm_live_[32 random characters]
const randomBytes = crypto.randomBytes(32).toString('hex');
return `ctm_live_${randomBytes}`;
}
function hashApiKey(apiKey) {
// Store only hashed version in database
return crypto.createHash('sha256').update(apiKey).digest('hex');
}
3. Checkout Session Handler
async function handleCheckoutComplete(session) {
const { customer_email, customer, subscription, metadata } = session;
// Generate API key
const apiKey = generateApiKey();
const hashedApiKey = hashApiKey(apiKey);
// Determine plan from metadata or price
const plan = metadata.plan || 'free'; // Set in Stripe product metadata
// Create user record
const userDoc = {
email: customer_email,
stripeCustomerId: customer,
subscriptionId: subscription,
plan: plan,
status: 'active',
apiKeyHash: hashedApiKey,
monthlyUsage: 0,
createdAt: new Date(),
updatedAt: new Date()
};
// Save to Firestore
await db.collection('subscriptions').doc(customer).set(userDoc);
// Send welcome email with API key
await sendWelcomeEmail(customer_email, apiKey, plan);
console.log(`New ${plan} subscription created for ${customer_email}`);
}
4. Firestore Schema
// Collection: subscriptions
// Document ID: Stripe Customer ID (cus_xxxxx)
{
email: "user@example.com",
stripeCustomerId: "cus_xxxxx",
subscriptionId: "sub_xxxxx",
plan: "free" | "pro",
status: "active" | "canceled" | "past_due",
apiKeyHash: "sha256_hash_of_api_key",
monthlyUsage: 0,
usageResetDate: "2025-02-01T00:00:00Z",
createdAt: "2025-01-23T20:30:00Z",
updatedAt: "2025-01-23T20:30:00Z",
canceledAt: null,
features: {
maxConversions: 50, // 50 for free, 10000 for pro
maxFileSize: 5242880, // 5MB for free, 52428800 for pro (50MB)
priority: false, // true for pro
dashboard: false // true for pro
}
}
Email Templates
Welcome Email (Free Tier)
async function sendWelcomeEmailFree(email, apiKey) {
const template = `
Subject: Welcome to Convert To Markdown - Your API Key Inside!
Hi there!
Welcome to Convert To Markdown! Your free account is now active.
Your API Key:
${apiKey}
Important: Save this key securely - we can't retrieve it for you!
What's included in your free plan:
50 conversions per month
5MB max file size
All file formats supported
Basic email support
Getting Started:
1. Add your API key to requests: x-api-key: ${apiKey}
2. Try your first conversion:
curl -X POST https://convert-to-markdown.knowcode.tech/xlsx-converter \\
-H "x-api-key: ${apiKey}" \\
-F "file=@your-file.xlsx"
Need help? Check our docs: https://convert-to-markdown.knowcode.tech/api
Happy converting!
The Convert To Markdown Team
`;
// Send using Gmail API
await sendEmail(email, template);
}
Welcome Email (Pro Tier)
async function sendWelcomeEmailPro(email, apiKey) {
const template = `
Subject: Welcome to Convert To Markdown Pro!
Hi there!
Welcome to Convert To Markdown Pro! Your account is ready for serious document processing.
Your API Key:
${apiKey}
Important: Save this key securely - we can't retrieve it for you!
Your Pro benefits:
10,000 conversions per month (200x more!)
50MB max file size (10x larger!)
Usage dashboard access
Priority processing
Priority email support
Advanced API features
Getting Started:
1. View your dashboard: https://convert-to-markdown.knowcode.tech/dashboard
2. Add your API key to requests: x-api-key: ${apiKey}
3. Try a large file conversion!
Manage your subscription anytime:
https://billing.stripe.com/p/login/${CUSTOMER_PORTAL_ID}
Questions? Reply to this email for priority support.
Welcome aboard!
The Convert To Markdown Team
`;
await sendEmail(email, template);
}
Stripe Configuration
1. Create Products in Stripe
# Using Stripe CLI (from deployment scripts)
stripe products create \
--name="Convert to Markdown API - Free" \
--description="Free tier with 50 conversions/month" \
--metadata[plan]="free" \
--metadata[conversions_limit]="50"
stripe products create \
--name="Convert to Markdown API - Pro" \
--description="Pro tier with 10,000 conversions/month" \
--metadata[plan]="pro" \
--metadata[conversions_limit]="10000"
2. Create Prices
# Free tier - $0/month subscription
stripe prices create \
--product=prod_free_xxxxx \
--unit-amount=0 \
--currency=usd \
--recurring[interval]=month
# Pro tier - $10/month subscription
stripe prices create \
--product=prod_pro_xxxxx \
--unit-amount=1000 \
--currency=usd \
--recurring[interval]=month
3. Create Checkout Links
In Stripe Dashboard:
- Go to Payment Links
- Create new payment link
- Select the appropriate price
- Configure:
- Collect email: Required
- Allow promotion codes: Optional
- After payment: Show success page
- Metadata: Add
plan: free
orplan: pro
4. Update Pricing Page
Replace placeholders in docs/pricing.md
:
YOUR_FREE_TIER_CHECKOUT_LINK
→ Free tier payment linkYOUR_PRO_TIER_CHECKOUT_LINK
→ Pro tier payment linkYOUR_STRIPE_CUSTOMER_PORTAL_LINK
→ Customer portal URL
API Key Validation
In your conversion functions, validate API keys:
async function validateApiKey(apiKey) {
if (!apiKey || !apiKey.startsWith('ctm_live_')) {
return { valid: false, error: 'Invalid API key format' };
}
const hashedKey = hashApiKey(apiKey);
// Find subscription by API key hash
const snapshot = await db.collection('subscriptions')
.where('apiKeyHash', '==', hashedKey)
.where('status', '==', 'active')
.limit(1)
.get();
if (snapshot.empty) {
return { valid: false, error: 'Invalid or inactive API key' };
}
const subscription = snapshot.docs[0].data();
const docId = snapshot.docs[0].id;
// Check usage limits
const limits = {
free: { conversions: 50, fileSize: 5 * 1024 * 1024 },
pro: { conversions: 10000, fileSize: 50 * 1024 * 1024 }
};
const planLimits = limits[subscription.plan];
if (subscription.monthlyUsage >= planLimits.conversions) {
return { valid: false, error: 'Monthly conversion limit reached' };
}
return {
valid: true,
subscription: subscription,
docId: docId,
limits: planLimits
};
}
Monthly Usage Reset
Create a Cloud Scheduler job to reset usage:
// functions/reset-usage/index.js
exports.resetMonthlyUsage = async (req, res) => {
const batch = db.batch();
const snapshot = await db.collection('subscriptions')
.where('status', '==', 'active')
.get();
snapshot.forEach(doc => {
batch.update(doc.ref, {
monthlyUsage: 0,
usageResetDate: new Date()
});
});
await batch.commit();
console.log(`Reset usage for ${snapshot.size} active subscriptions`);
res.json({ reset: snapshot.size });
};
Testing
1. Test Webhook Locally
# Forward Stripe events to local function
stripe listen --forward-to localhost:8080/stripe-webhook
# Trigger test event
stripe trigger checkout.session.completed
2. Test API Key Validation
// Test valid key
const result = await validateApiKey('ctm_live_test123...');
console.log(result); // { valid: true, subscription: {...} }
// Test invalid key
const invalid = await validateApiKey('invalid_key');
console.log(invalid); // { valid: false, error: 'Invalid API key format' }
3. Integration Test
# 1. Create test subscription
# 2. Verify webhook received
# 3. Check Firestore for user record
# 4. Verify email sent
# 5. Test API key works
# 6. Verify usage tracking
Security Considerations
- API Key Storage: Never store plain text API keys
- Webhook Validation: Always verify Stripe signatures
- Rate Limiting: Implement per-IP rate limits for public endpoints
- CORS: Configure appropriate CORS policies
- Encryption: Use HTTPS for all endpoints
Monitoring
Track these metrics:
- Webhook success/failure rate
- API key validation performance
- Email delivery success
- Usage patterns by plan
- Conversion success rates
Next Steps
- Replace placeholder URLs in pricing page
- Deploy webhook function
- Configure Stripe webhook endpoint
- Test end-to-end flow
- Monitor first real subscriptions
Questions? See the main deployment documentation or contact lindsay@knowcode.tech