{"id":51401566,"url":"https://github.com/4msar/cf-webhooks","last_synced_at":"2026-07-04T07:03:25.264Z","repository":{"id":356094540,"uuid":"1231024197","full_name":"4msar/cf-webhooks","owner":"4msar","description":"Cloudflare D1 DB webhook receiver","archived":false,"fork":false,"pushed_at":"2026-05-19T05:56:23.000Z","size":217,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-19T08:18:41.647Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://webhook.msar.dev","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/4msar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-06T14:50:43.000Z","updated_at":"2026-05-19T05:58:49.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/4msar/cf-webhooks","commit_stats":null,"previous_names":["4msar/cf-webhooks"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/4msar/cf-webhooks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4msar%2Fcf-webhooks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4msar%2Fcf-webhooks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4msar%2Fcf-webhooks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4msar%2Fcf-webhooks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/4msar","download_url":"https://codeload.github.com/4msar/cf-webhooks/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4msar%2Fcf-webhooks/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35112709,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-04T07:03:23.408Z","updated_at":"2026-07-04T07:03:25.255Z","avatar_url":"https://github.com/4msar.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Webhook Dashboard\n\nA production-ready webhook inspection dashboard built on Cloudflare Workers, D1, React, and TailwindCSS v4.\n\nInspired by Stripe Logs, Vercel Logs, and GitHub webhook inspection tools.\n\n---\n\n## Stack\n\n| Layer | Technology |\n|---|---|\n| Runtime | Cloudflare Workers |\n| Database | Cloudflare D1 (SQLite) |\n| Frontend | React 19 + Vite |\n| Styling | TailwindCSS v4 |\n| Routing | React Router v7 |\n| Deployment | Wrangler |\n\n---\n\n## Features\n\n- **Webhook receiver** — `POST /api/:app-name/webhook` accepts JSON payloads, reads event type from `x-event-type` header or request body\n- **Events dashboard** — sticky table with ID, event type, and timestamp columns\n- **Accordion rows** — click any row to expand payload JSON and request headers inline\n- **Auto-refresh** — configurable polling at 20s / 30s / 60s without full page reload\n- **Manual refresh** — refresh button with loading indicator\n- **Copy JSON** — one-click copy for both payload and headers\n- **Row count + last updated** — visible in the dashboard toolbar\n- **App management** — create and look up apps by slug from the home page\n- **Auto-pruning** — keeps only the 100 most recent events per app\n\n---\n\n## Project Structure\n\n```\nwebhook/\n├── migrations/\n│   └── 0001_initial.sql       # D1 schema: webhook_apps + webhook_events\n├── worker/\n│   ├── index.ts               # Worker entry point + URL router\n│   ├── db.ts                  # D1 database operations\n│   └── handlers/\n│       ├── apps.ts            # GET /api/apps/:slug, POST /api/apps\n│       ├── events.ts          # GET /api/:slug/events\n│       └── webhook.ts         # POST /api/:slug/webhook\n├── src/\n│   ├── main.tsx               # React entry + BrowserRouter\n│   ├── App.tsx                # Client-side routes\n│   ├── index.css              # Tailwind v4 + accordion styles\n│   ├── pages/\n│   │   ├── Home.tsx           # App name lookup / create page\n│   │   └── Dashboard.tsx      # Events dashboard + toolbar\n│   └── components/\n│       ├── EventsTable.tsx    # Table with accordion row expansion\n│       └── JsonViewer.tsx     # Syntax-highlighted JSON + copy button\n├── wrangler.jsonc\n├── INSTRUCTIONS.md            # Full feature spec + design decisions\n└── package.json\n```\n\n---\n\n## Getting Started\n\n### 1. Install dependencies\n\n```bash\nyarn install\n```\n\n### 2. Create the D1 database\n\n```bash\nnpx wrangler d1 create webhook_db\n```\n\nCopy the `database_id` from the output and update `wrangler.jsonc`:\n\n```jsonc\n\"d1_databases\": [\n  {\n    \"binding\": \"DB\",\n    \"database_name\": \"webhook_db\",\n    \"database_id\": \"\u003cyour-database-id-here\u003e\",\n    \"migrations_dir\": \"migrations\"\n  }\n]\n```\n\n### 3. Apply migrations locally\n\n```bash\nnpx wrangler d1 migrations apply webhook_db --local\n```\n\n### 4. Start the dev server\n\n```bash\nyarn dev\n```\n\n---\n\n## Deployment\n\n```bash\n# Apply migrations on remote D1\nnpx wrangler d1 migrations apply webhook_db\n\n# Build and deploy\nyarn deploy\n```\n\n---\n\n## API Reference\n\n### Check if an app exists\n```\nGET /api/apps/:slug\n```\nReturns `200` with app data, or `404` if not found.\n\n### Create a new app\n```\nPOST /api/apps\nContent-Type: application/json\n\n{ \"name\": \"my-app\" }\n```\nReturns `201` with the created app. Returns `200` if the slug already exists.\n\n### Send a webhook\n```\nPOST /api/:slug/webhook\nContent-Type: application/json\nX-Event-Type: payment.succeeded   (optional)\n\n{ ...your payload... }\n```\nStores the event and returns `{ \"success\": true }`.\n\n### List events\n```\nGET /api/:slug/events\n```\nReturns the latest 100 events ordered by `created_at DESC`.\n\n---\n\n### Send a webhook\n```\nPOST /api/:slug/webhook\nContent-Type: application/json\nX-Event-Type: payment.succeeded   (optional)\n\n{ ...your payload... }\n```\nStores the event and returns `{ \"success\": true }`.\n\n**Basic event with header-based event type:**\n```bash\ncurl -X POST https://webhook.msar.dev/api/bill-app/webhook \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-Event-Type: payment.succeeded\" \\\n  -d '{\n    \"id\": \"evt_001\",\n    \"amount\": 4900,\n    \"currency\": \"usd\",\n    \"customer\": \"cus_abc123\",\n    \"status\": \"succeeded\"\n  }'\n```\n\n**Event type from body field:**\n```bash\ncurl -X POST https://webhook.msar.dev/api/bill-app/webhook \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"event_type\": \"invoice.created\",\n    \"invoice_id\": \"inv_20260506\",\n    \"due_date\": \"2026-06-06\",\n    \"amount_due\": 12000,\n    \"customer_email\": \"user@example.com\"\n  }'\n```\n\n**Bill paid event:**\n```bash\ncurl -X POST https://webhook.msar.dev/api/bill-app/webhook \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-Event-Type: bill.paid\" \\\n  -d '{\n    \"bill_id\": \"bill_789\",\n    \"paid_at\": \"2026-05-06T14:40:00Z\",\n    \"amount\": 25000,\n    \"method\": \"bank_transfer\",\n    \"reference\": \"TXN-2026-88812\"\n  }'\n```\n\n**Send multiple events quickly (bash loop):**\n```bash\nfor event in \"bill.created\" \"bill.due\" \"bill.paid\" \"bill.overdue\"; do\n  curl -s -X POST https://webhook.msar.dev/api/bill-app/webhook \\\n    -H \"Content-Type: application/json\" \\\n    -H \"X-Event-Type: $event\" \\\n    -d \"{\\\"event\\\": \\\"$event\\\", \\\"timestamp\\\": \\\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\\\"}\" \\\n    \u0026\u0026 echo \" ✓ $event\"\ndone\n```\n\n## Security\n\n- Payloads are capped at **1 MB**\n- `Authorization` and `Cookie` headers are stripped before storage\n- All JSON is HTML-escaped before rendering (no XSS)\n- Uses `crypto.randomUUID()` — no `Math.random()`\n\n---\n\n## Development Notes\n\n- Run `yarn cf-typegen` after changing bindings in `wrangler.jsonc` to regenerate `worker-configuration.d.ts`\n- The React SPA is served via the `ASSETS` binding; all `/api/*` routes are handled by the Worker\n- Tailwind v4 requires no config file — configured entirely via `@tailwindcss/vite` plugin\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F4msar%2Fcf-webhooks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F4msar%2Fcf-webhooks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F4msar%2Fcf-webhooks/lists"}