# 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 (
Bedankt voor je aanmelding bij Premium.