Architecture
Event Pipeline
Detailed flow from transactional outbox to webhook delivery.
Event Pipeline Architecture
End-to-End Flow
1. Go API Handler
│ INSERT INTO outbox_events (same transaction as domain write)
▼
2. PostgreSQL WAL
│ Logical replication slot captures outbox_events INSERT
▼
3. Debezium CDC Connector
│ Reads WAL, transforms to CDC event envelope
│ Publishes to RabbitMQ exchange: kaiten.events
▼
4. RabbitMQ
│ Fanout exchange → consumer queue
▼
5. Dapr Sidecar (pubsub component)
│ Subscribes to queue, HTTP POST to api endpoint
▼
6. Go Webhooks Worker (/events endpoint)
│ Parses Debezium CDC envelope
│ Extracts: event_name, event_type, data, org_id
│ Calls Svix API: svix.Message.Create()
▼
7. Svix
│ Routes to org's webhook endpoints by event type
│ Retries with exponential backoff
▼
8. Customer's Webhook Endpointsoutbox_events Table
CREATE TABLE outbox_events (
id UUID PRIMARY KEY,
event_name TEXT NOT NULL, -- e.g. "CUSTOMER_CREATION"
event_type TEXT NOT NULL, -- e.g. "com.kaiten.customer.v1.created"
payload JSONB NOT NULL, -- domain data
organization_id UUID NOT NULL,
traceparent TEXT, -- OpenTelemetry trace context
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);Debezium Configuration
Debezium is configured to watch the outbox_events table using the outbox event router SMT (Single Message Transform):
- Source: PostgreSQL WAL logical replication
- Table:
public.outbox_events - Routing:
event_type→ RabbitMQ routing key
Svix Integration
Each Kaiten organization maps to a Svix Application. When an organization is created, a corresponding Svix app is created. Webhook endpoints and event types are managed through the Svix SDK.
Guarantees
- At-least-once delivery: domain write + outbox insert are atomic
- No message loss: Debezium reads from the PostgreSQL WAL
- Ordered per table: events are processed in WAL insertion order
- Idempotent processing: webhooks may be delivered more than once

