The Mental Model
Your server has a private phone line.
When you integrate with a third-party service like Stripe, you're giving them a phone number—a URL on your server. When something important happens (a payment succeeds, a subscription cancels), they call that number and tell you about it. That call is a webhook.
A third-party service sends an HTTP POST to your server when an event happens.
A secret string that proves you're authorized to use someone else's service.
The process of exchanging keys and verifying identity before any data flows.
A URL on your server that listens for incoming webhook calls.
// The invisible conversation
User → clicks "Pay" → Stripe
(browser → stripe.com)
Stripe → charges card → Success
(stripe processes payment)
Stripe → POST → your-app.com/api/webhook
(stripe "calls" your server)
Your Server → unlocks account
(you update the database)
// The user never sees steps 3-4.
// They just see: "Payment successful!"
How Webhooks Work
The Request
You call them. Your app reaches out to an external API to get or send data. Like ordering food at a restaurant.
// YOU initiate the call
const response = await fetch(
'https://api.openai.com/v1/chat'
);
The Webhook
They call you. The external service sends data to your server when something happens on their side. Like a delivery notification arriving at your door.
// THEY send data to your endpoint
export async function POST(req) {
const event = await req.json();
// handle the event...
}
API Keys
Your secret backstage pass.
An API key is a long, random string that proves your identity to an external service. It's like a backstage pass at a concert—it lets you in, and if someone steals it, they can pretend to be you.
That's why API keys live in .env.local, never in your frontend code, and never in Git.
Secret Key (Server Only)
Starts with sk_ — never expose to the browser.
Publishable Key (Client OK)
Starts with pk_ — safe to use in the browser.
Test vs Live Keys
sk_test_ = sandbox. sk_live_ = real money.
# Stripe
STRIPE_SECRET_KEY=sk_test_51N3x...
NEXT_PUBLIC_STRIPE_KEY=pk_test_51N3x...
# OpenAI
OPENAI_API_KEY=sk-proj-a8kF2...
# Resend (email)
RESEND_API_KEY=re_123abc...
# Webhook secret (to verify incoming calls)
STRIPE_WEBHOOK_SECRET=whsec_test_...
# NEVER commit this file to Git!
# Add .env.local to .gitignore
The Handshake
Three steps to trust.
Every third-party integration follows the same handshake ritual. Once you see the pattern, you'll recognize it everywhere—Stripe, OpenAI, Resend, Twilio, all of them.
Sign up for the API
Create an account on the service's website. Verify your identity. Agree to their terms.
They give you a key
In their dashboard, you'll find API keys. Copy the secret key into your .env.local.
Include the key in every request
Every API call you make includes an Authorization header with your key. That's how they know it's you.
// Step 3: Include the key in every request
const response = await fetch(
'https://api.openai.com/v1/chat/completions',
{
method: 'POST',
headers: {
'Authorization':
`Bearer ${process.env.OPENAI_API_KEY}`,
'Content-Type':
'application/json',
},
body: JSON.stringify({
model: 'gpt-4',
messages: [{ role: 'user', content: 'Hello' }],
}),
},
);
// The "Bearer" prefix is an HTTP convention.
// It says: "I bear this token as proof."
Common Patterns
✗ Integration Traps
"I'll just hardcode the API key"
Now it's in your Git history forever. Anyone with access to the repo can steal it. Use .env.local.
"I'll call the API from the browser"
Your secret key would be visible in DevTools → Network tab. API calls with secrets must go through your server.
"I don't need to verify webhooks"
Anyone could send fake webhook events to your endpoint. Always verify the signature using the webhook secret.
"I'll use the live key for testing"
You just charged a real customer $500. Use test keys (sk_test_) until you're ready for production.
✓ The Clean Integration
Keys in .env.local, accessed via process.env
Never in code, never in Git. Your .gitignore should include .env*.local by default.
API calls go through your server (Route Handlers)
Create /api/chat/route.ts and call OpenAI from there. The browser never sees your key.
Verify every incoming webhook
Use the webhook secret to validate the signature. Stripe, Clerk, and others all provide verification helpers.
Separate test and production environments
Different .env files for dev vs prod. Test keys locally, live keys on Vercel.
The Exercises
Open the doors. Connect to the outside world.
The Postman
Your First API Call
- →Install Postman or use
curlin the terminal - →Call
jsonplaceholder.typicode.com/posts/1 - →Read the JSON response. Notice the structure.
The Webhook Listener
Receiving Data
- →Create
app/api/webhook/route.ts - →Export a POST function that logs the request body
- →Test it with
curl -X POSTfrom the terminal
The Key Vault
Environment Variables
- →Create a
.env.localfile with a test API key - →Access it in a server component via
process.env - →Confirm it's invisible in the browser (check page source)
📋 Quick Reference Cheatsheet
Webhook Endpoint
export async function POST(req) { }Auth Header
Authorization: Bearer ${KEY}Env Variable
process.env.STRIPE_SECRET_KEY