{"id":47685208,"url":"https://github.com/vadymkykalo/webhook-platform","last_synced_at":"2026-04-02T14:46:51.115Z","repository":{"id":328974210,"uuid":"1116834952","full_name":"vadymkykalo/webhook-platform","owner":"vadymkykalo","description":"Reliable webhook delivery at scale. Automatic retries, HMAC signatures, real-time dashboard.","archived":false,"fork":false,"pushed_at":"2026-03-16T08:27:13.000Z","size":8235,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-16T08:36:36.951Z","etag":null,"topics":["docker","event-driven","kafka","outbox-pattern","postgres","react","redis","spring-boot","webhooks"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vadymkykalo.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","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":"2025-12-15T12:56:42.000Z","updated_at":"2026-03-16T08:27:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/vadymkykalo/webhook-platform","commit_stats":null,"previous_names":["vadymkykalo/webhook-platform"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/vadymkykalo/webhook-platform","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vadymkykalo%2Fwebhook-platform","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vadymkykalo%2Fwebhook-platform/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vadymkykalo%2Fwebhook-platform/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vadymkykalo%2Fwebhook-platform/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vadymkykalo","download_url":"https://codeload.github.com/vadymkykalo/webhook-platform/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vadymkykalo%2Fwebhook-platform/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31308406,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["docker","event-driven","kafka","outbox-pattern","postgres","react","redis","spring-boot","webhooks"],"created_at":"2026-04-02T14:46:50.405Z","updated_at":"2026-04-02T14:46:51.104Z","avatar_url":"https://github.com/vadymkykalo.png","language":"Java","readme":"\u003cdiv align=\"center\"\u003e\n\n# Hookflow\n\n**Self-hosted webhook infrastructure. Outgoing delivery + incoming ingress.**\n\n[![CI](https://github.com/vadymkykalo/webhook-platform/actions/workflows/ci.yml/badge.svg)](https://github.com/vadymkykalo/webhook-platform/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Java 17](https://img.shields.io/badge/Java-17-orange)]()\n[![Spring Boot 3.2](https://img.shields.io/badge/Spring%20Boot-3.2-green)]()\n[![Docker](https://img.shields.io/badge/Docker-Required-2496ED?logo=docker\u0026logoColor=white)](https://www.docker.com/)\n\n```bash\ngit clone https://github.com/vadymkykalo/webhook-platform.git \u0026\u0026 cd webhook-platform \u0026\u0026 make up\n```\n\n**Dashboard** → http://localhost:5173 \u0026nbsp;|\u0026nbsp; **API Docs** → http://localhost:8080/swagger-ui.html\n\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"docs/img.png\" alt=\"Hookflow Dashboard\" width=\"100%\"\u003e\n\u003c/div\u003e\n\n---\n\n## Quick Start\n\n**Prerequisites:** Docker 20.10+, Docker Compose v2+, `make`\n\n```bash\nmake up                   # Start everything\n# Open http://localhost:5173, register, create project, get API key\nmake verify-link          # Get email verification link from logs\n```\n\n```bash\n# Send your first event\ncurl -X POST http://localhost:8080/api/v1/projects/{projectId}/events \\\n  -H \"X-API-Key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\": \"user.signup\", \"payload\": {\"userId\": \"usr_42\"}}'\n```\n\n---\n\n## Features\n\n### Outgoing Delivery\n- **Transactional outbox → Kafka** — at-least-once, zero event loss\n- **FIFO ordering** per endpoint (Redis ordering buffer + sequence numbers)\n- **6-tier retry** — 1m, 5m, 15m, 1h, 6h, 24h\n- **DLQ** with one-click reprocess · **Circuit breaker** per endpoint\n- **HMAC-SHA256** signatures · **mTLS** · **Endpoint verification** (challenge-response)\n\n### Incoming Ingress\n- **Public URLs** — `/ingress/{token}` per source, provider-specific signature verification\n- **Built-in providers** — Stripe, GitHub, GitLab, Shopify, Slack + generic HMAC\n- **Multi-destination forwarding** with auth (Bearer / Basic / custom header)\n- **Payload transformation** — JSONPath · **Per-source rate limiting** · **Full audit trail**\n\n### CLI \u0026 Local Tunnel\n- **`hookflow listen 3000`** — receive webhooks on localhost during development, no deploy needed\n- **WebSocket tunnel** — public URL → backend → WS → CLI → `localhost:PORT` → response back\n- **Device code login** — secure auth without typing passwords in terminal (like `gh auth login`)\n- **Event replay** — re-deliver past events for debugging · **Event tail** — follow events in real-time\n- **Tunnel management** — list, close, status · **Auto-reconnect** with exponential backoff\n- **Plan-based tunnel limits** — FREE tier = disabled, paid plans get per-org active tunnel caps\n- **Bandwidth metering** — per-org monthly tunnel traffic tracked in Redis\n- **Config profiles** — switch between staging/production servers without re-login\n\n### Platform\n- **Schema Registry** — JSON Schema per event type, breaking change detection, WARN/BLOCK policies\n- **Wildcard subscriptions** — `order.*`, `order.**`, `**`\n- **Multi-tenancy** — Organizations → Projects → Endpoints, RBAC (Owner/Developer/Viewer)\n- **AES-256-GCM** encryption for all secrets · **SSRF protection** · **API keys** with scoping\n- **Prometheus metrics** · **Correlation IDs** · **Audit logging**\n- **SDKs** — [Node.js](./sdks/node), [Python](./sdks/python), [PHP](./sdks/php) · **Request Bin** built-in\n\n---\n\n## Architecture\n\n```mermaid\ngraph TB\n    subgraph \"Third-Party Providers\"\n        Stripe[Stripe]\n        GitHub[GitHub]\n        Shopify[Shopify]\n    end\n\n    subgraph \"Your Infrastructure\"\n        App[Your Application]\n        Svc1[Internal Service A]\n        Svc2[Internal Service B]\n    end\n    \n    subgraph \"Hookflow\"\n        UI[Dashboard\u003cbr/\u003eReact + Vite]\n        API[API Service\u003cbr/\u003eSpring Boot]\n        DB[(PostgreSQL\u003cbr/\u003eEvents · Deliveries · Outbox\u003cbr/\u003eIncoming Events · Forward Attempts)]\n        Redis[(Redis\u003cbr/\u003eRate Limits · Ordering Buffer)]\n        Kafka[Kafka\u003cbr/\u003eDelivery Topics · Forward Topics · Retry · DLQ]\n        Worker[Worker Service\u003cbr/\u003eSpring Boot]\n    end\n    \n    subgraph \"Customer Endpoints\"\n        EP1[Endpoint A]\n        EP2[Endpoint B]\n    end\n    \n    App --\u003e|POST /api/v1/events| API\n    UI  --\u003e|REST API| API\n    API --\u003e|Transactional Write| DB\n    API --\u003e|Outbox Publish| Kafka\n    Kafka --\u003e|Consume Deliveries| Worker\n    Worker --\u003e|POST + HMAC| EP1\n    Worker --\u003e|POST + HMAC| EP2\n    \n    Stripe --\u003e|POST /ingress/tok_stripe| API\n    GitHub --\u003e|POST /ingress/tok_github| API\n    Shopify --\u003e|POST /ingress/tok_shopify| API\n    API --\u003e|Verify Signature + Persist| DB\n    Kafka --\u003e|Consume Forwards| Worker\n    Worker --\u003e|Forward + Auth| Svc1\n    Worker --\u003e|Forward + Auth| Svc2\n    \n    API --\u003e|Rate Limit| Redis\n    Worker --\u003e|Read/Update| DB\n    Worker --\u003e|Ordering Buffer| Redis\n    \n    style API fill:#4CAF50\n    style Worker fill:#2196F3\n    style UI fill:#FF9800\n    style DB fill:#9C27B0\n    style Kafka fill:#F44336\n    style Redis fill:#DC382D\n```\n\n| Service | Port | Role |\n|---------|------|------|\n| **API** | `8080` | Event ingestion, webhook ingress, REST API, outbox publisher |\n| **Worker** | `8081` | Kafka consumer, HTTP delivery, forwarding, retry scheduling |\n| **UI** | `5173` | Admin dashboard (React / Vite / shadcn/ui) |\n| **PostgreSQL** | `5432` | Events, deliveries, incoming events, outbox |\n| **Kafka** | `9092` | Dispatch + 6 retry tiers + forward dispatch/retry + DLQ |\n| **Redis** | `6379` | Rate limiting, FIFO ordering, circuit breaker |\n\n### Outgoing Delivery Flow\n\n```mermaid\nsequenceDiagram\n    participant App as Your Application\n    participant API as API Service\n    participant DB as PostgreSQL\n    participant Kafka as Kafka\n    participant Worker as Worker\n    participant EP as Customer Endpoint\n    \n    App-\u003e\u003eAPI: POST /events\n    API--\u003e\u003eApp: 202 Accepted\n    API-\u003e\u003eDB: INSERT event + deliveries + outbox (single TX)\n    \n    Note over API: Outbox publisher polls every 100ms\n    API-\u003e\u003eKafka: Publish DeliveryMessage\n    API-\u003e\u003eDB: Mark outbox PUBLISHED\n    \n    Kafka-\u003e\u003eWorker: Consume from deliveries.dispatch\n    Worker-\u003e\u003eDB: Load delivery + endpoint + secret\n    Worker-\u003e\u003eEP: POST payload + HMAC-SHA256 signature\n    \n    alt 2xx Response\n        EP--\u003e\u003eWorker: 200 OK\n        Worker-\u003e\u003eDB: Status = SUCCESS\n    else 4xx/5xx / Timeout\n        EP--\u003e\u003eWorker: 503 / timeout\n        Worker-\u003e\u003eKafka: Publish to deliveries.retry.1m\n        Note over Worker: Retry delays: 1m, 5m, 15m, 1h, 6h, 24h\n    else All retries exhausted\n        Worker-\u003e\u003eKafka: Publish to deliveries.dlq\n        Worker-\u003e\u003eDB: Status = DLQ\n    end\n```\n\n### Incoming Ingress Flow\n\n```mermaid\nsequenceDiagram\n    participant Provider as Third-Party Provider\n    participant API as API Service\n    participant DB as PostgreSQL\n    participant Kafka as Kafka\n    participant Worker as Worker\n    participant Dest as Your Internal Service\n\n    Provider-\u003e\u003eAPI: POST /ingress/{token}\n    API-\u003e\u003eDB: Load IncomingSource by token\n    \n    alt Signature verification enabled\n        API-\u003e\u003eAPI: Verify signature (Stripe/GitHub/Shopify/Slack/HMAC)\n    end\n    \n    API-\u003e\u003eDB: INSERT IncomingEvent (headers, body, IP, verified status)\n\n    alt Signature invalid\n        API--\u003e\u003eProvider: 401 Unauthorized\n    else Valid\n        API--\u003e\u003eProvider: 202 Accepted\n        API-\u003e\u003eDB: INSERT ForwardAttempts + OutboxMessages (single TX)\n        API-\u003e\u003eKafka: Publish to incoming.forward.dispatch\n        \n        Kafka-\u003e\u003eWorker: Consume forward message\n        Worker-\u003e\u003eDest: POST body + auth headers\n        \n        alt 2xx\n            Worker-\u003e\u003eDB: Status = SUCCESS\n        else Failure\n            Worker-\u003e\u003eDB: Schedule retry\n        end\n    end\n```\n\n### CLI Tunnel Flow\n\n```mermaid\nsequenceDiagram\n    participant Dev as Developer (localhost)\n    participant CLI as Hookflow CLI\n    participant API as API Service\n    participant WS as WebSocket Hub\n    participant Provider as Third-Party Provider\n\n    Dev-\u003e\u003eCLI: hookflow listen 3000\n    CLI-\u003e\u003eAPI: POST /api/v1/tunnels (JWT auth)\n    API--\u003e\u003eCLI: 201 {slug, wsUrl}\n    CLI-\u003e\u003eWS: Connect WSS /ws/tunnel (slug in handshake)\n    WS--\u003e\u003eCLI: Connected ✓\n\n    Note over CLI,WS: Tunnel active — public URL ready\n\n    Provider-\u003e\u003eAPI: POST /tunnel/{slug} (webhook payload)\n    API-\u003e\u003eWS: Forward request via WebSocket\n    WS-\u003e\u003eCLI: TunnelRequestMessage (headers, body)\n    CLI-\u003e\u003eDev: POST http://localhost:3000 (forwarded)\n    Dev--\u003e\u003eCLI: 200 OK + response body\n    CLI-\u003e\u003eWS: TunnelResponseMessage\n    WS-\u003e\u003eAPI: Response back\n    API--\u003e\u003eProvider: 200 OK\n\n    Note over CLI: Auto-reconnect on disconnect\u003cbr/\u003eExponential backoff up to 2min\n```\n\n---\n\n## Deployment\n\n### Development\n\n```bash\nmake up              # Start all (embedded PostgreSQL)\nmake up-external-db  # External/managed DB\nmake down            # Stop (data preserved)\nmake logs            # Follow logs\nmake doctor          # Pre-flight checks\n```\n\n### Production\n\n```bash\ncp .env.dist .env    # Edit with real secrets\nmake up-prod         # Production overrides\nmake health          # Verify services\n```\n\nAll env vars documented in [`.env.dist`](./.env.dist). Run `make doctor` before production.\n\n### Monitoring\n\n```bash\nmake monitoring-up        # Start Prometheus + Grafana\nmake monitoring-down      # Stop monitoring\nmake monitoring-logs      # Follow monitoring logs\nmake nuke CONFIRM=YES     # Destroy everything (platform + monitoring)\n```\n\n| Service | URL | Credentials |\n|---------|-----|-------------|\n| **Grafana** | http://localhost:3001 | `hookflow` / `hookflow_monitor_2024` |\n| **Prometheus** | http://localhost:9090 | — |\n\n4 dashboards auto-provisioned: **Overview**, **Worker \u0026 Circuit Breaker**, **JVM / Micrometer**, **Kafka**.\n\n### Key Commands\n\n```bash\nmake health               # Check all services\nmake backup-db            # Backup database\nmake restore-db FILE=...  # Restore from backup\nmake shell-db             # Open psql shell\nmake dev-api              # Quick rebuild API + tail logs\nmake verify-link          # Email verification link (dev)\nmake reset-link           # Password reset link (dev)\nmake invite-link          # Member invite link (dev)\nmake nuke CONFIRM=YES     # Destroy everything (platform + monitoring)\n```\n\n### CLI Commands\n\n```bash\n# Install CLI (auto-installs Java 17 if missing)\ncurl -fsSL https://raw.githubusercontent.com/vadymkykalo/webhook-platform/main/webhook-platform-cli/install.sh | bash\n\n# Or build from source (optional)\n# mvn clean package -pl webhook-platform-cli -am -DskipTests\n# alias hookflow='java -jar webhook-platform-cli/target/webhook-platform-cli-1.0.0-SNAPSHOT.jar'\n\n# Auth\nhookflow login                             # Device code flow (browser approve)\nhookflow login --email u@x.com --password  # Direct login\n\n# Local tunnel\nhookflow listen 3000                       # Forward webhooks to localhost:3000\nhookflow listen 3000 --project \u003cid\u003e        # Associate with project\n\n# Tunnel management\nhookflow tunnels list                      # List active tunnels\nhookflow tunnels close \u003csessionId\u003e         # Close tunnel\nhookflow tunnels status                    # Active tunnels, bandwidth, pending requests\n\n# Events\nhookflow events \u003cprojectId\u003e               # Recent events\nhookflow events \u003cprojectId\u003e --follow      # Tail in real-time\nhookflow replay \u003cprojectId\u003e --dry-run     # Estimate replay\nhookflow replay \u003cprojectId\u003e               # Replay last 24h\n\n# Diagnostics\nhookflow status                            # Auth, health, active tunnels\nhookflow config show                       # Current config\nhookflow config set backend-url \u003curl\u003e      # Change backend\nhookflow config profile list               # List all profiles\nhookflow config profile create staging --url https://staging.example.com\nhookflow config profile use staging         # Switch to staging\nhookflow config profile use default         # Switch back\nhookflow config profile delete staging      # Remove profile\n```\n\n---\n\n## Troubleshooting \u0026 Configuration\n\n### Quick Reference\n\n| What | How | Default |\n|------|-----|---------|\n| **Swagger UI** | http://localhost:8080/swagger-ui.html | Enabled in dev |\n| **Dashboard** | http://localhost:5173 | — |\n| **Grafana** | http://localhost:3001 | `hookflow` / `hookflow_monitor_2024` |\n| **Prometheus** | http://localhost:9090 | — |\n| **API Health** | http://localhost:8080/actuator/health | — |\n| **Worker Health** | http://localhost:8081/actuator/health | — |\n| **Metrics** | http://localhost:8080/actuator/prometheus | — |\n\n### Common Issues\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eAPI won't start — \"WEBHOOK_ENCRYPTION_KEY must be at least 32 characters\"\u003c/b\u003e\u003c/summary\u003e\n\nYour `.env` key is too short. Fix:\n```bash\ncp .env.dist .env   # Fresh copy with valid defaults\nmake up\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eEmail/password/invite links in dev mode\u003c/b\u003e\u003c/summary\u003e\n\nWith `EMAIL_ENABLED=false` (default), all links go to API logs:\n\n```bash\nmake verify-link   # Email verification\nmake reset-link    # Password reset (expires in 1h)\nmake invite-link   # Member invite (expires in 48h)\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eEnable Swagger UI\u003c/b\u003e\u003c/summary\u003e\n\nSwagger is disabled by default. To enable, add to `.env`:\n```env\nSWAGGER_ENABLED=true\n```\nThen restart: `make dev-api` or `make up`. Open http://localhost:8080/swagger-ui.html\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eEndpoint creation fails with 500\u003c/b\u003e\u003c/summary\u003e\n\nSet `TEST_ENDPOINT_BASE_URL=http://api:8080` in `.env` (must match Docker service name).\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eKafka topics not created\u003c/b\u003e\u003c/summary\u003e\n\n```bash\nmake up  # Auto-creates topics\n# or manually:\ndocker exec webhook-kafka kafka-topics --create --topic deliveries.dispatch --partitions 12 --bootstrap-server localhost:9092\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eMonitoring: Grafana shows \"No data\"\u003c/b\u003e\u003c/summary\u003e\n\n1. Make sure the main platform is running first: `make up \u0026\u0026 make wait-healthy`\n2. Then start monitoring: `make monitoring-up`\n3. Wait ~30s for first scrape. Check Prometheus targets: http://localhost:9090/targets\n\u003c/details\u003e\n\n### SMTP / Email Setup\n\n```env\nEMAIL_ENABLED=true\nEMAIL_FROM=noreply@yourdomain.com\nSMTP_HOST=smtp.gmail.com        # or your SMTP provider\nSMTP_PORT=587\nSMTP_USERNAME=your@email.com\nSMTP_PASSWORD=app_password\nSMTP_AUTH=true\nSMTP_STARTTLS=true\n```\n\n### Production Checklist\n\n\u003e **Before going live**, override these in your `.env`:\n\n| Variable | Dev Default | Production Value |\n|----------|-------------|------------------|\n| `APP_ENV` | `development` | `production` |\n| `WEBHOOK_ENCRYPTION_KEY` | `dev_encryption_key_...` | **Random 32+ char string** |\n| `WEBHOOK_ENCRYPTION_SALT` | `dev_encryption_salt_...` | **Random 16+ char string** |\n| `JWT_SECRET` | `dev_jwt_secret_...` | **Random 32+ char string** |\n| `POSTGRES_PASSWORD` | `webhook_secret` | **Strong unique password** |\n| `REDIS_PASSWORD` | `redis_secret` | **Strong unique password** |\n| `SWAGGER_ENABLED` | `true` | `false` |\n| `WEBHOOK_ALLOW_PRIVATE_IPS` | `true` | `false` |\n| `EMAIL_ENABLED` | `false` | `true` + SMTP config |\n| `CORS_ALLOWED_ORIGINS` | `*` | `https://yourdomain.com` |\n| `DB_SSL_MODE` | `disable` | `require` or `verify-full` |\n| `LOG_LEVEL` | `INFO` | `WARN` |\n\n```bash\n# Generate secure secrets\nopenssl rand -base64 32   # For encryption key\nopenssl rand -base64 24   # For JWT secret\nopenssl rand -base64 18   # For DB/Redis passwords\n```\n\n---\n\n## License\n\n[MIT](./LICENSE) © Vadym Kykalo\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvadymkykalo%2Fwebhook-platform","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvadymkykalo%2Fwebhook-platform","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvadymkykalo%2Fwebhook-platform/lists"}