# Les 17 — Externe APIs in diepte ## Slide Overzicht (Klas A — 3 uur fysiek, demo-driven) **Lesvorm:** Tim demonstreert klassikaal. Studenten kijken. Zelf bouwen = huiswerk. **Demo-app:** Subscription-dashboard (Stripe Checkout + Resend email) **Vervolg op:** Les 16 — MCP servers **Aansluit op:** Les 18 — Supabase Auth + RLS --- ## Slide 1: Title ### Les 17 — Externe APIs in diepte **Visual:** "Les 17" BLUE, "Externe APIs in diepte" BLACK, subtitle "OAuth, webhooks, Stripe en Resend — productie-patronen" --- ## Slide 2: Terugblik ### Waar staan we? **Lessen 11-16:** - AI SDK, tool calling, agents, RAG, Cursor+Vercel deploy, MCP **Externe APIs deden we al in Les 15 (PokéAPI):** simpel, geen key, GET requests. Productie is anders. **Vandaag — de echte wereld:** - Login met Google/GitHub (OAuth) - Iemand doet een betaling — server moet dat weten (webhooks) - Betaalde APIs zoals Stripe en Resend - Wat als de API faalt? Retry-logic - Hoe doe je dat veilig en netjes? --- ## Slide 3: Planning ### Vandaag — 180 minuten | Onderwerp | Duur | |-----------|------| | Terugblik + API-types | 15 min | | Theorie: OAuth basics | 20 min | | Theorie: Webhooks + verifying | 20 min | | **Live Demo 1** — Stripe Checkout integratie | 25 min | | **Live Demo 2** — Webhook ontvangen + verify | 25 min | | **Pauze** | 15 min | | **Live Demo 3** — Resend transactional email | 25 min | | **Live Demo 4** — Rate limit + retry pattern | 15 min | | Productie checklist | 10 min | | Lesopdracht + Huiswerk | 10 min | --- ## Slide 4: Drie soorten externe API integratie ### Welke heb je nodig? | Type | Voorbeeld | Wat moet je weten | |------|-----------|-------------------| | **Simple fetch** | PokéAPI, Open-Meteo | URL + JSON parsing (Les 15) | | **API key in header** | OpenAI, Tavily, Anthropic | Env vars, server-side fetch | | **OAuth flow** | Google login, GitHub | Redirect dance, tokens, sessies | | **Webhook** | Stripe, GitHub, Slack | POST endpoint, signature verify | **Vandaag focus op de laatste twee** — daar komen 80% van productie-API-bugs vandaan. --- ## Slide 5: OAuth — wat en waarom ### Login met Google / GitHub / etc. **Het probleem:** je wilt dat gebruikers inloggen zonder zelf wachtwoorden te beheren. **OAuth flow (kort):** ``` 1. User klikt "Login met GitHub" 2. Redirect naar github.com/login/oauth/authorize 3. User logt in op GitHub + geeft toestemming 4. GitHub redirect terug naar JOUW app met code 5. JOUW server exchanged code voor access_token 6. JOUW server haalt user-data op met token 7. JOUW server zet sessie / cookie ``` **In Next.js — drie populaire libraries:** - **Auth.js (NextAuth v5)** — meest gebruikt - **Better-Auth** — moderne challenger, eenvoudiger - **Clerk** — paid, alles inclusief Voor dit vak: Auth.js — open source, gratis, werkt overal. --- ## Slide 6: Webhooks — wat en waarom ### Externe service belt JOUW app **Het probleem:** soms moet je weten wanneer iets gebeurt op een externe service. **Voorbeeld Stripe:** - User doet betaling op Stripe Checkout - Stripe verwerkt betaling (zou kunnen falen!) - Stripe stuurt POST request naar JOUW webhook URL - Jouw server update database, stuurt email, etc. **Webhook = jouw HTTP endpoint** dat door externe service wordt aangeroepen. **Cruciaal: signature verification** Zonder verificatie kan iedereen je webhook URL aanroepen met fake data. ```typescript // Stripe doet dit zo: const sig = request.headers.get("stripe-signature"); const event = stripe.webhooks.constructEvent( body, sig, process.env.STRIPE_WEBHOOK_SECRET ); // Throws als signature niet klopt ``` --- ## Slide 7: Wat we vandaag bouwen ### Subscription-dashboard **Mini-app:** Premium subscription met Stripe + email confirmaties. **Tech:** - Next.js 16 - Stripe (test mode — geen echt geld) - Resend (transactional email — gratis tier 3000/maand) - ngrok of `localhost.run` voor lokaal webhook testen **Flow:** ``` [Pricing page] → [Stripe Checkout] → [Webhook] → [Database + Email] ``` **Endpoints we bouwen:** - `POST /api/checkout` — start Stripe sessie - `POST /api/webhook/stripe` — ontvang Stripe events - `POST /api/email` — verzend Resend mail (intern aangeroepen) --- ## Slide 8: LIVE DEMO 1 — Stripe Checkout ### ~25 min **Wat ik laat zien:** 1. Stripe account opzetten (test mode) 2. Pricing page met "Subscribe" button 3. `app/api/checkout/route.ts` — maak Checkout session ```typescript import Stripe from "stripe"; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); export async function POST(req: Request) { const { priceId, email } = await req.json(); const session = await stripe.checkout.sessions.create({ mode: "subscription", line_items: [{ price: priceId, quantity: 1 }], customer_email: email, success_url: `${process.env.APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${process.env.APP_URL}/pricing`, }); return Response.json({ url: session.url }); } ``` 4. Client redirect naar `session.url` 5. Test in browser met Stripe test cards (4242 4242 4242 4242) 6. Stripe Dashboard — sessie zichtbaar, betaling staat als "complete" --- ## Slide 9: LIVE DEMO 2 — Webhook ontvangen ### ~25 min **Wat ik laat zien:** 1. Webhook secret ophalen van Stripe Dashboard 2. `app/api/webhook/stripe/route.ts`: ```typescript import { headers } from "next/headers"; export async function POST(req: Request) { const body = await req.text(); // RAW body, niet json() const sig = headers().get("stripe-signature")!; let event; try { event = stripe.webhooks.constructEvent( body, sig, process.env.STRIPE_WEBHOOK_SECRET! ); } catch (err) { return new Response("Webhook signature failed", { status: 400 }); } if (event.type === "checkout.session.completed") { const session = event.data.object; await supabase.from("subscriptions").insert({ customer_email: session.customer_email, stripe_session_id: session.id, status: "active", }); } return Response.json({ received: true }); } ``` 3. **Lokaal testen:** `stripe listen --forward-to localhost:3000/api/webhook/stripe` 4. Stripe CLI geeft webhook secret terug — in `.env.local` 5. Doe een test-checkout — zie webhook binnenkomen in CLI én database update **Belangrijk:** `req.text()` voor RAW body — niet `req.json()`. Signature wordt over bytes berekend. --- ## Slide 10: Pauze ### 15 min --- ## Slide 11: LIVE DEMO 3 — Resend email ### ~25 min **Wat ik laat zien:** 1. Resend account, API key, eigen domain (of `onboarding@resend.dev` voor demo) 2. `pnpm add resend react-email` 3. Email template als React component: ```tsx // emails/WelcomeEmail.tsx export function WelcomeEmail({ name }: { name: string }) { return (

Welkom {name}!

Bedankt voor je aanmelding bij Premium.

); } ``` 4. Verzend in webhook handler: ```typescript import { Resend } from "resend"; import { WelcomeEmail } from "@/emails/WelcomeEmail"; const resend = new Resend(process.env.RESEND_API_KEY!); await resend.emails.send({ from: "onboarding@resend.dev", to: session.customer_email!, subject: "Welkom bij Premium!", react: WelcomeEmail({ name: session.customer_email!.split("@")[0] }), }); ``` 5. Test checkout → webhook → mail komt aan 6. Resend dashboard — email zichtbaar in logs, status delivered --- ## Slide 12: LIVE DEMO 4 — Rate limit + retry ### ~15 min **Wat ik laat zien:** **Probleem:** externe APIs kunnen falen. Wat doe je? **Pattern 1 — Exponential backoff retry:** ```typescript async function retry(fn: () => Promise, attempts = 3): Promise { for (let i = 0; i < attempts; i++) { try { return await fn(); } catch (err) { if (i === attempts - 1) throw err; await new Promise(r => setTimeout(r, 2 ** i * 1000)); } } throw new Error("unreachable"); } const data = await retry(() => fetch("https://api.example.com/data").then(r => r.json()) ); ``` **Pattern 2 — Rate limit met `Upstash Ratelimit`:** ```typescript import { Ratelimit } from "@upstash/ratelimit"; import { Redis } from "@upstash/redis"; const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(10, "10s"), }); // In API route const { success } = await ratelimit.limit(userId); if (!success) return new Response("Too many requests", { status: 429 }); ``` Beide patronen zijn cruciaal voor productie. Niet doen = service crash bij verkeerd gedrag. --- ## Slide 13: Productie checklist ### Voordat je live gaat **API keys:** - ✓ Test-keys voor preview, prod-keys voor productie - ✓ Vercel scoping per environment (Les 15) - ✓ Rotate keys eens per kwartaal **Webhooks:** - ✓ Signature verification ALTIJD - ✓ Idempotency — zelfde event 2x ontvangen = OK - ✓ Log alle webhook payloads (helpt bij debug) - ✓ Return 200 snel — verwerking async **Errors:** - ✓ Exponential backoff retry voor transient fails - ✓ Rate limit eigen endpoints - ✓ Monitor met Sentry / LogRocket / Vercel Analytics **Security:** - ✓ Keys nooit in client-bundle (geen `NEXT_PUBLIC_`) - ✓ Webhook endpoints accepteren alleen verified signatures - ✓ User-input validatie (Zod) --- ## Slide 14: Lesopdracht + Huiswerk ### Eigen externe API integreren **Lesopdracht (30 min):** - Stripe test account opzetten - Pricing page + Checkout endpoint - Doe een test-betaling met test card - Geen webhook nog — alleen success_url demo **Huiswerk (~2 uur, voor Les 18):** - A: Webhook handler + signature verify - B: Resend transactional email na betaling - C: Retry logic voor externe API call - D: `APIS.md` met: - Welke externe APIs jouw app gebruikt - Hoe webhooks getest worden (Stripe CLI of ngrok screenshots) - Voorbeeld van een failed call + hoe afgevangen - Eindopdracht-toepassing: welke API ga jij integreren **Bonus:** OAuth login met GitHub/Google via Auth.js --- ## Slide 15: Volgende les + Afsluiting ### Vragen? **Vandaag gezien:** - Drie soorten externe API integratie - OAuth flow basics - Webhooks ontvangen + signature verify - Stripe Checkout in productie-patroon - Resend transactional email - Rate limit + retry - Productie checklist **Volgende les (Les 18 — laatste!): Supabase Auth + RLS** - Multi-user apps met login - Magic link / password / social - RLS policies — per-user data isolation - Protected routes + middleware - Combo van alles wat we geleerd hebben **Vragen?** --- ## Slide Summary | # | Title | Type | |---|-------|------| | 1 | Title | Opening | | 2 | Terugblik | Recap | | 3 | Planning | 180-min | | 4 | 3 soorten API integratie | Theorie | | 5 | OAuth basics | Theorie | | 6 | Webhooks + verifying | Theorie | | 7 | Wat we bouwen | Intro | | 8 | **DEMO 1** — Stripe Checkout | Demo | | 9 | **DEMO 2** — Webhook ontvangen | Demo | | 10 | Pauze | Break | | 11 | **DEMO 3** — Resend email | Demo | | 12 | **DEMO 4** — Rate limit + retry | Demo | | 13 | Productie checklist | Reflectie | | 14 | Lesopdracht + Huiswerk | Praktijk | | 15 | Afsluiting | Closing | --- ## Bronnen - **Stripe Checkout docs:** https://docs.stripe.com/checkout - **Stripe webhooks:** https://docs.stripe.com/webhooks - **Stripe CLI:** https://docs.stripe.com/stripe-cli - **Resend:** https://resend.com/docs - **React Email:** https://react.email/ - **Auth.js:** https://authjs.dev/ - **Upstash Ratelimit:** https://upstash.com/docs/oss/sdks/ts/ratelimit - **Better-Auth:** https://www.better-auth.com/