By the end of this tutorial you'll have a Git repo, an HTTP server that can receive requests, a working linter, a one-page design doc sketching the three main tables, and the foundation for reliable webhook delivery.
← Back to Module 01 overviewnvm / fnm / volta.git --version should print something.gh CLI installed and authenticated, or be ready to create a repo via the web UI.docker run -d -e POSTGRES_PASSWORD=password -p 5432:5432 postgres:16webhook-delivery with main as the default branch.http://localhost:3000.npm run lint and npm run dev both work.docs/design.md documenting the three main tables: webhooks, events, deliveries.main requiring PRs.From an empty parent folder:
mkdir webhook-delivery && cd webhook-delivery git init -b main gh repo create webhook-delivery --public --source=. --remote=origin # Or create via github.com and clone if you prefer
git status shows "On branch main".npm init -y npm install express typescript ts-node dotenv pg npm install --save-dev @types/node @types/express eslint prettier
ls node_modules | grep express shows express is installed.node_modules/. Create a .gitignore first (see Step 3).Create .gitignore:
node_modules/ .env .env.local dist/ *.log .DS_Store
Create .eslintrc.json:
{
"extends": "eslint:recommended",
"parser": "@typescript-eslint/parser",
"parserOptions": { "ecmaVersion": 2020 },
"env": { "node": true, "es2020": true },
"rules": { "no-unused-vars": "warn" }
}
Create .editorconfig:
root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true indent_style = space indent_size = 2
ls -la | grep git and ls -la | grep eslint.Create tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Create src/index.ts:
import express from 'express';
const app = express();
app.use(express.json());
app.get('/', (req, res) => {
res.json({ message: 'Webhook Delivery Service is running' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
src/index.ts and contains the server code.Update package.json with these scripts:
"scripts": {
"dev": "ts-node src/index.ts",
"build": "tsc",
"lint": "eslint src --ext .ts",
"lint:fix": "eslint src --ext .ts --fix"
}
Run the server:
npm run dev
In another terminal, test it:
curl http://localhost:3000
curl http://localhost:3000 returns {"message":"Webhook Delivery Service is running"}. Linting passes: npm run lint.npm run lint:fix to auto-fix most issues.Create docs/design.md:
# Webhook Delivery Service — Design Doc ## Problem Teams need a reliable way to send events (webhooks) to customer endpoints. If the customer's server is down, events must be retried; if it keeps failing, events go to a dead-letter queue for manual recovery. ## Goals - Reliably deliver events to customer endpoints - Support exponential backoff retries (1s, 4s, 16s, 64s, 256s) - Implement dead-letter queue for permanent failures - Track delivery status and provide audit logs - Support at least 1000 events/sec to 100 webhooks ## Non-Goals - End-to-end encryption (HTTPS covers this) - Built-in webhook transformations - Event sampling/filtering (can add later) ## Data Model ### webhooks table ``` id (UUID) url (string) - customer endpoint secret (string) - for HMAC signing event_types (string[]) - which events this webhook receives created_at (timestamp) updated_at (timestamp) ``` ### events table ``` id (UUID) type (string) - e.g., "order.created" payload (JSON) - the event data timestamp (timestamp) created_at (timestamp) ``` ### deliveries table ``` id (UUID) webhook_id (UUID) - foreign key event_id (UUID) - foreign key status (enum) - pending, processing, success, failed attempts (int) - how many retries so far last_error (string) next_retry_at (timestamp) created_at (timestamp) updated_at (timestamp) ``` ## Idempotency Strategy Each delivery has a unique `delivery_id`. Customers should use this to deduplicate if they receive the same event twice. Server guarantees: best-effort delivery (at-least-once, not exactly-once). ## Open Questions - How long to retain events in the log? - Should we support batch deliveries? - Authentication: API key, OAuth, or both?
docs/design.md and describes the three tables.git add . git commit -m "init: scaffold webhook delivery service - Set up TypeScript, Express, linting - Basic health-check endpoint - Design doc with data model" git push -u origin main
On GitHub, go to Settings → Branches and enable branch protection for main. Require PR reviews and status checks.
git log --oneline shows your commit. GitHub web shows the repo with branch protection enabled.You've laid the foundation. Next, you'll implement the core webhook API: registration endpoints and event emission. Head over to Module 02 when you're ready.