A webhook receiver and processor for Taddy podcast data. Receives webhook events via an Express server, enqueues them in Valkey, and processes them with a background worker that upserts data into PostgreSQL.
Taddy webhook → Express server → Valkey queue → Worker → PostgreSQL
- Server (
src/server.ts) — ReceivesPOST /webhooks/taddyrequests, validates the webhook secret, and enqueues the payload. - Queue (
src/queue/) — Uses Valkey (Redis-compatible) as a FIFO queue viaLPUSH/BRPOP. - Worker (
src/worker.ts) — Polls the queue and upsertsPODCASTSERIESandPODCASTEPISODEevents into PostgreSQL using Drizzle ORM.
- Node.js (v18+)
- PostgreSQL
- Valkey or Redis
- ngrok (for local development)
# Clone the repo
git clone <repo-url>
cd webhook-example-taddy
# Install dependencies
npm install
# Copy the example env file and fill in your values
cp .env.example .env| Variable | Description | Default |
|---|---|---|
PORT |
Server listen port | 3000 |
WEBHOOK_SECRET |
Secret sent by Taddy in x-taddy-webhook-secret header |
— |
POSTGRES_USER |
PostgreSQL username | user |
POSTGRES_PASSWORD |
PostgreSQL password | password |
POSTGRES_DB |
PostgreSQL database name | podcastdata |
Note: When running with Docker Compose,
DATABASE_URLandVALKEY_URLare set automatically using the compose service names. ThePOSTGRES_*variables configure the PostgreSQL container.
Install Docker (via docker-ce), enable the service, and allow your user to run containers without sudo:
sudo apt install -y docker.io docker-compose-v2
sudo systemctl enable --now docker
sudo usermod -aG docker $USERStart all services (PostgreSQL, Valkey, server, and worker) with Docker Compose:
cp .env.example .env # edit .env with your values
docker compose up --buildStop and clean up:
docker compose downTo remove the persisted PostgreSQL data as well:
docker compose down -v| Script | Description |
|---|---|
npm run dev |
Start dev server with hot reload (tsx watch) |
npm run build |
Compile TypeScript to dist/ |
npm start |
Run compiled server |
npm run worker |
Start dev worker with hot reload |
npm run worker:start |
Run compiled worker |
npm run db:generate |
Generate Drizzle migration files |
npm run db:migrate |
Run pending migrations |
npm run db:push |
Push schema directly to database |
npm run db:studio |
Open Drizzle Studio GUI |
- Taddy sends a
POSTrequest to/webhooks/taddywith a JSON payload containinguuid,taddyType,action,timestamp,data, andmatchingFilters. - The server validates the
x-taddy-webhook-secretheader againstWEBHOOK_SECRET. - The payload is pushed onto a Valkey list (
webhook:events). - The worker blocks on the queue with
BRPOP(5-second timeout) and processes events as they arrive. - Based on
taddyType, the worker upserts the record into thepodcastseriesorpodcastepisodetable using Drizzle'sonConflictDoUpdate(keyed onuuid).