https://github.com/dorianneto/hookyard
The Open Source WaaS that never drops a package.
https://github.com/dorianneto/hookyard
monolith react symfony waas webhook webhooks
Last synced: about 2 months ago
JSON representation
The Open Source WaaS that never drops a package.
- Host: GitHub
- URL: https://github.com/dorianneto/hookyard
- Owner: dorianneto
- Created: 2026-03-28T11:11:46.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-04-17T19:52:24.000Z (2 months ago)
- Last Synced: 2026-04-17T21:32:09.112Z (2 months ago)
- Topics: monolith, react, symfony, waas, webhook, webhooks
- Language: PHP
- Homepage: https://hookyard.dorianneto.com
- Size: 520 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Roadmap: ROADMAP.md
Awesome Lists containing this project
README
# About HookYard
Open Source **Webhook-as-a-Service (WaaS)** platform that receives webhooks from third-party services and fans them out to user-defined destination URLs with automatic retries and a delivery dashboard.
The metaphor
> _A rail yard is a place where trains (incoming webhooks) arrive, get sorted by destination, and dispatched out on different tracks (endpoints). Cars can be re-routed if a track fails — just like retries. The yard master (the worker) orchestrates everything without the sender needing to know the details._
| Rail yard concept | WaaS concept |
|-------------------|--------------|
| Incoming train | Inbound webhook (POST /in/{uuid}) |
| Rail yard | HookYard platform |
| Track / destination | Endpoint URL |
| Dispatch | Fan-out to endpoints |
| Re-routing on failure | Automatic retry (5 attempts) |
| Yard master | Queue worker |
| Cargo manifest | Delivery dashboard |
## Stack
| Layer | Technology |
|-------|-----------|
| Backend | PHP 8.4 / Symfony 7 |
| Frontend | React 18 + TypeScript + Vite + shadcn/ui + Tailwind CSS v4 |
| Database | PostgreSQL 17 |
| Queue | Symfony Messenger (AWS SQS) |
| Deployment | AWS Elastic Beanstalk (monolith) |
## Architecture
The backend follows **Hexagonal Architecture** (Ports & Adapters), keeping business logic completely isolated from the framework.
See diagram
```mermaid
graph TD
subgraph Driving["Driving Adapters (input)"]
HTTP["HTTP Controllers\nsrc/Controller/"]
Worker["Queue Worker\n(Messenger Handlers)"]
end
subgraph Application["Application Layer\nsrc/Application/"]
UC["Use Cases\nUseCase/"]
Ports["Ports (interfaces)\nPort/"]
end
subgraph Domain["Domain Layer\nsrc/Domain/"]
Entities["Entities & Value Objects\nUser, Source, Endpoint\nEvent, DeliveryAttempt"]
Rules["Business Rules\n& Exceptions"]
end
subgraph Driven["Driven Adapters (output)"]
DB["Doctrine Repositories\nsrc/Infrastructure/Persistence/"]
Security["Security Handlers\nsrc/Security/"]
Ext["External HTTP\n(outbound deliveries)"]
end
HTTP --> UC
Worker --> UC
UC --> Ports
UC --> Entities
Ports -.->|implemented by| DB
Ports -.->|implemented by| Ext
DB --> Security
style Domain fill:#1a3a2a,color:#fff,stroke:#2d6a4f
style Application fill:#1a2a3a,color:#fff,stroke:#2d4f6a
style Driving fill:#2a1a1a,color:#fff,stroke:#6a2d2d
style Driven fill:#2a2a1a,color:#fff,stroke:#6a5d2d
```
### Layer Rules
- **Domain** (`src/Domain/`) — pure PHP: entities, value objects, domain exceptions. Zero imports from `Symfony\` or `Doctrine\`.
- **Application** (`src/Application/`) — use cases that orchestrate domain objects through port interfaces.
- **Infrastructure** (`src/Infrastructure/`, `src/Controller/`, `src/Security/`) — Symfony/Doctrine adapters that implement the ports and expose HTTP endpoints.
### Webhook Delivery Flow
```
POST /in/{uuid}
→ persist Event
→ enqueue one Messenger message per active Endpoint
→ return 200 OK immediately
Worker:
→ POST raw body + headers to Endpoint URL
→ adds X-Webhook-Event-Id header
→ 5 attempts: immediate → 30s → 5m → 30m → 2h
→ recomputes events.status atomically (pending / delivered / failed)
```
## Development
### Start all services
```bash
docker compose up -d
```
### Backend (inside container or host)
```bash
php bin/console doctrine:migrations:diff # generate migration from entity changes
php bin/console doctrine:migrations:migrate # apply pending migrations
php bin/phpunit # run all tests
```
### Frontend
```bash
npm run build # production build → public/
npm run watch # Vite dev watch
```
UI is built with **shadcn/ui** components and **Tailwind CSS v4**. All components live in `frontend/src/components/ui/`.
## Data Model
See diagram
```mermaid
erDiagram
users {
int id PK
string email
string password
timestamp created_at
}
sources {
int id PK
int user_id FK
string name
uuid inbound_uuid
timestamp created_at
}
endpoints {
int id PK
int source_id FK
string url
timestamp created_at
}
events {
int id PK
int source_id FK
string method
json headers
text body
enum status
timestamp received_at
}
event_endpoint_deliveries {
int id PK
int event_id FK
int endpoint_id FK
enum status
timestamp created_at
timestamp updated_at
}
delivery_attempts {
int id PK
int event_id FK
int endpoint_id FK
smallint attempt_number
smallint status_code
text response_body
int duration_ms
timestamp attempted_at
}
users ||--o{ sources : owns
sources ||--o{ endpoints : has
sources ||--o{ events : receives
events ||--o{ event_endpoint_deliveries : tracks
endpoints ||--o{ event_endpoint_deliveries : tracks
event_endpoint_deliveries ||--o{ delivery_attempts : logs
```
### Key relationships
- A **Source** belongs to one user and has many **Endpoints** and many **Events**.
- When an Event arrives, one **`event_endpoint_deliveries`** row is created per active Endpoint (unique on `(event_id, endpoint_id)`).
- Each delivery row accumulates up to 5 **`delivery_attempts`** (exponential backoff: immediate → 30s → 5m → 30m → 2h).
- `events.status` (`pending` / `delivered` / `failed`) is a denormalized cache recomputed atomically from all delivery rows every time a delivery row changes.
