Last reviewed: 2026-02-22
Customer → Stripe Checkout → Stripe Webhook → Vercel Serverless Function
↓
Resend Email API → Customer inbox
↓
Thank-you page → Static PDF download
- Email address (from Stripe checkout)
- Name (from Stripe checkout, optional)
| System | Data | Retention | Access |
|---|---|---|---|
| Stripe | Email, name, payment info | Stripe retention policy | Stripe Dashboard |
| Resend | Email (delivery logs) | Resend retention policy | Resend Dashboard |
| Vercel | Email, name (function logs) | 1 hour (free tier) | Vercel Dashboard |
No customer data is stored in our codebase, database, or filesystem.
| Secret | Location | Purpose |
|---|---|---|
| STRIPE_SECRET_KEY | Vercel env vars | Stripe API access |
| STRIPE_WEBHOOK_SECRET | Vercel env vars | Webhook signature verification |
| RESEND_API_KEY | Vercel env vars | Email delivery |
| ANTHROPIC_API_KEY | Vercel env vars | Echo AI chat feature |
- Rotate all keys quarterly or immediately upon suspected compromise
- Stripe webhook secret: rotate via Stripe Dashboard > Webhooks > Signing secret
- Resend API key: rotate via Resend Dashboard > API Keys
- After rotation: update Vercel env vars, redeploy
All accounts in the deployment chain must have MFA enabled:
| Account | Service | MFA Status | Last Verified |
|---|---|---|---|
| GitHub | Code hosting, auto-deploy | [ ] Verify | — |
| Vercel | Hosting, env vars, serverless | [ ] Verify | — |
| Stripe | Payments, webhook config | [ ] Verify | — |
| Resend | Email delivery | [ ] Verify | — |
TODO: check and fill in MFA status for each account before shipping.
- Authentication: Stripe signature verification on every request (HMAC-SHA256)
- Idempotency: In-memory Set tracks processed checkout session IDs. Limitation: resets on Vercel cold starts. Acceptable at current volume. Upgrade to KV store if duplicate deliveries are confirmed.
- Unknown products: Webhook returns 400 error if
product_slugmetadata is present but unrecognized. Falls back to default product only when metadata is entirely absent. - Error handling: Email delivery failures return 500 to Stripe, triggering retry. Signature failures return 400.
| Risk | Severity | Decision | Trigger to Revisit |
|---|---|---|---|
| Public download URLs (no auth) | Low | Accepted — $49 PDFs, piracy cost < prevention cost | Higher price tier or volume |
| In-memory idempotency | Low | Accepted — warm-instance optimization at current volume | First confirmed duplicate |
| No rate limiting on webhook | Low | Accepted — Stripe signature check blocks unsigned requests | High volume or abuse detected |
- Immediately rotate the compromised key in the service dashboard
- Update Vercel environment variables
- Redeploy:
git pushtriggers auto-deploy - Review access logs in the compromised service for unauthorized activity
- If customer data was exposed: notify affected customers within 72 hours
- Check Vercel function logs for error details
- Check Stripe Dashboard > Webhooks > Recent events for failed deliveries
- Cross-reference with Resend Dashboard for email delivery status
- Manually resend via Resend if customer was charged but not delivered
- Primary: Erin Stanley (evokesupports@icloud.com)
- Stripe support: dashboard.stripe.com/support
- Vercel support: vercel.com/support
- Resend support: resend.com/support