Skip to content

Commit b3bb7b4

Browse files
tonyxiaoclaude
andcommitted
docs(temporal): update for three-server architecture (webhook server split out)
Webhook ingress is now a separate server (`sync-service webhook`) rather than a route on the service API. Update all diagrams and prose to reflect: - Three servers: Webhook Server (public), Sync Service (internal), Engine API - Architecture overview shows WHRoute in its own subgraph - Webhook event flow diagram adds the Webhook Server participant - Running Locally adds Terminal 4 for `sync-service webhook --port 4030` - "Why two servers?" → "Why three servers?" table with Exposure row - Files table includes webhook-app.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Committed-By-Agent: claude
1 parent eb4435a commit b3bb7b4

1 file changed

Lines changed: 40 additions & 24 deletions

File tree

docs/pages/service/temporal.md

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ title: Temporal Workflow Architecture
66

77
When Temporal is enabled, sync lifecycle is managed by durable workflows instead of running in-process. The workflow orchestrates setup, continuous reconciliation, live event processing, and teardown.
88

9-
Two servers run independently:
9+
Three servers run independently:
1010

11-
- **Service API** — config CRUD, credential management, webhook ingress, config resolution
11+
- **Webhook Server** — public-facing; receives raw Stripe events and signals the matching Temporal workflow(s)
12+
- **Service API** — internal; config CRUD, credential management, config resolution
1213
- **Engine API** — stateless sync execution (setup, sync, teardown via `X-Sync-Params`)
1314

1415
Activities call the service for config resolution, then the engine for execution.
@@ -22,10 +23,13 @@ graph TD
2223
StripeWH["Stripe Webhooks"]
2324
end
2425
25-
subgraph Service["Sync Service"]
26+
subgraph WebhookServer["Webhook Server (public)"]
27+
WHRoute["POST /webhooks/{cred_id}"]
28+
end
29+
30+
subgraph Service["Sync Service (internal)"]
2631
CRUD["/syncs CRUD"]
2732
Resolve["GET /syncs/{id}<br/>?include_credentials=true"]
28-
WHRoute["/webhooks/{cred_id}"]
2933
end
3034
3135
subgraph EngineAPI["Sync Engine"]
@@ -44,10 +48,12 @@ graph TD
4448
Sheets["Google Sheets"]
4549
end
4650
51+
%% Stripe → Webhook Server → Temporal
52+
StripeWH --> WHRoute
53+
WHRoute -- "signal: stripe_event" --> Workflow
54+
4755
%% Service → Workflow
4856
CRUD -- "start / signal: delete" --> Workflow
49-
WHRoute -- "signal: stripe_event" --> Workflow
50-
StripeWH --> WHRoute
5157
5258
%% Workflow → Activities
5359
Workflow --> Worker
@@ -101,19 +107,21 @@ sequenceDiagram
101107

102108
## Webhook Event Flow
103109

104-
The webhook path crosses three boundaries (Stripe → Service → Temporal → Engine):
110+
The webhook path crosses four boundaries (Stripe → Webhook Server → Temporal → Service → Engine):
105111

106112
```mermaid
107113
sequenceDiagram
108114
participant Stripe
109-
participant Service as Sync Service
115+
participant Webhook as Webhook Server
110116
participant Workflow as syncWorkflow
111117
participant Activity
118+
participant Service as Sync Service
112119
participant Engine as Sync Engine
113120
114-
Stripe->>Service: POST /webhooks/{credential_id}
115-
Note over Service: Find all syncs with<br/>matching credential_id
116-
Service->>Workflow: signal('stripe_event', event)
121+
Stripe->>Webhook: POST /webhooks/{credential_id}
122+
Note over Webhook: Scan syncs.json for<br/>matching credential_id
123+
Webhook->>Workflow: signal('stripe_event', event)
124+
Webhook-->>Stripe: 200 ok (fire-and-forget)
117125
Note over Workflow: Buffer event
118126
119127
Note over Workflow: Next loop iteration
@@ -193,16 +201,18 @@ stateDiagram-v2
193201

194202
## Key Design Decisions
195203

196-
### Why two servers?
204+
### Why three servers?
197205

198-
The service and engine have different responsibilities:
206+
Each server has a single, clearly scoped responsibility:
199207

200-
| | Sync Service | Sync Engine |
201-
| ----------- | --------------------------------------------------- | ------------------------------------------- |
202-
| **Purpose** | Config CRUD, credential management, webhook ingress | Stateless sync execution |
203-
| **State** | Stores configs, credentials | Manages cursor state via `selectStateStore` |
204-
| **Routes** | `/syncs`, `/credentials`, `/webhooks` | `/setup`, `/sync`, `/teardown` |
205-
| **Config** | Stored form (`credential_id` references) | Resolved form (`X-Sync-Params`) |
208+
| | Webhook Server | Sync Service | Sync Engine |
209+
| ----------- | --------------------------------------------------- | --------------------------------------------------- | ------------------------------------------- |
210+
| **Purpose** | Public webhook ingress; fan out signals to Temporal | Config CRUD, credential management, config resolution | Stateless sync execution |
211+
| **State** | None — reads config store to locate matching syncs | Stores configs, credentials | Manages cursor state via `selectStateStore` |
212+
| **Routes** | `POST /webhooks/{credential_id}` | `/syncs`, `/credentials` | `/setup`, `/sync`, `/teardown` |
213+
| **Exposure**| Public (Stripe POSTs here) | Internal | Internal |
214+
215+
The webhook server requires only a Temporal client and the config store (read-only) to fan out signals. It never touches credentials or runs connectors.
206216

207217
### Why activities resolve each time?
208218

@@ -261,17 +271,20 @@ temporal server start-dev
261271
# Terminal 2: Sync engine (stateless execution)
262272
sync-engine serve --port 4010
263273

264-
# Terminal 3: Sync service (config CRUD + Temporal)
265-
sync-service serve --temporal-address localhost:7233
274+
# Terminal 3: Sync service (config CRUD + config resolution)
275+
sync-service serve --port 4020 --temporal-address localhost:7233
276+
277+
# Terminal 4: Webhook server (public ingress)
278+
sync-service webhook --port 4030 --temporal-address localhost:7233
266279

267-
# Terminal 4: Worker
280+
# Terminal 5: Worker
268281
sync-service worker --temporal-address localhost:7233
269282
```
270283

271284
Create a sync — the workflow starts automatically:
272285

273286
```sh
274-
# Create sync
287+
# Create sync (via internal service API)
275288
curl -X POST http://localhost:4020/syncs \
276289
-H 'Content-Type: application/json' \
277290
-d '{
@@ -291,6 +304,8 @@ curl -X POST http://localhost:4020/syncs/<id>/resume
291304
curl -X DELETE http://localhost:4020/syncs/<id>
292305
```
293306

307+
Point Stripe's webhook dashboard at the **webhook server** (`http://your-host:4030/webhooks/{credential_id}`), not the service API.
308+
294309
## Testing
295310

296311
### Unit tests (stubbed activities)
@@ -326,10 +341,11 @@ curl -X DELETE http://localhost:4020/syncs/<id>
326341

327342
| File | Role |
328343
| ------------------------------------------------------ | ----------------------------------------------- |
344+
| `apps/service/src/api/webhook-app.ts` | `createWebhookApp` — standalone webhook ingress |
329345
| `apps/service/src/temporal/types.ts` | `RunResult`, `SyncActivities`, `WorkflowStatus` |
330346
| `apps/service/src/temporal/activities.ts` | Resolve from service, execute on engine |
331347
| `apps/service/src/temporal/workflows.ts` | Workflow: signals, queries, main loop |
332348
| `apps/service/src/temporal/worker.ts` | Worker factory |
333-
| `apps/service/src/cli/main.ts` | `worker` subcommand |
349+
| `apps/service/src/cli/main.ts` | `serve`, `webhook`, `worker` subcommands |
334350
| `apps/service/src/__tests__/temporal-workflow.test.ts` | Unit tests |
335351
| `e2e/temporal.test.ts` | E2E tests |

0 commit comments

Comments
 (0)