Architecture Overview
How the WATS packages, Graph request flow, and webhook flow fit together.
active · applies to 0.3.x-alpha-tooling · reviewed 2026-05-01
WATS is a Bun-first TypeScript toolkit for WhatsApp operations. It is built as small packages with explicit dependency direction, not one large framework package. You take the layers you need and ignore the rest.
Package layers
@wats/types
shared domain contracts
@wats/crypto @wats/graph
crypto seam graph client
adapters endpoints
@wats/core
typed updates, filters, router, listeners, WhatsApp facade@wats/core is the composition root: it composes @wats/graph, @wats/crypto, and @wats/types and never imports the application-edge packages. Application-edge packages sit ABOVE @wats/core and compose it — they normalize raw inputs into typed values and dispatch them:
@wats/http webhook HTTP boundary — verifies signatures and normalizes
raw webhooks into typed updates before dispatch (composes
@wats/core + @wats/crypto)
@wats/config config schema, YAML/JSON loading, env-secret references
@wats/service standalone webhook/API service, OpenAPI 3.1
@wats/cli init, validate, doctor, serve, openapi commandsApp-layer packages may depend on @wats/core; lower-level packages must not. The dependency arrows only point down — @wats/core never imports @wats/http. (@wats/http is an application-edge package, NOT a peer of @wats/graph: it sits above @wats/core and uses normalizeWebhookEnvelope from it.)
Request flow
A Graph request starts in either user code or a scoped client:
PhoneNumberClient,WABAClient, or a customdefineEndpointcallable builds path/query/body options.GraphClientvalidates the path, query, headers, base URL, API version, token, and body handling.- The injected
Transportsends the request. - Graph failures map into the typed error taxonomy. Catch by
instanceof, not by parsing Meta's prose.
Endpoint breadth grows without duplicating transport, auth, validation, or error plumbing.
Webhook flow
A webhook request enters through @wats/http:
- A runtime wrapper adapts Bun, Node, or Fetch
Requestshapes to the runtime-neutralWebhookAdapter. - The adapter verifies the Meta challenge or
X-Hub-Signature-256signature. normalizeWebhookEnvelopeturns the raw JSON body intoTypedUpdatevalues.- The supplied facade-shaped object dispatches each update.
TypedRouterevaluates listeners and handlers, preserving dispatch reports and isolating user-code failures.
The adapter acknowledges valid webhooks even when your handlers throw. Meta retries unacknowledged webhooks until you acknowledge them or die; a bug in your handler should not trigger that storm.
Extension points
Transportfor retries, tracing, auth refresh, mocks, and offline tests.defineEndpointfor adding typed Graph endpoints ahead of first-class wrappers.TypedFilterand filter combinators for handler/listener matching.RouterObserverand webhook logger hooks for observability.CryptoProviderfor Node/Bun/WebCrypto portability.
Invariants
- Public API names are camelCase-only.
- Public APIs are async-only where work may cross I/O or runtime seams.
- Low-level packages stay runtime-portable; runtime-specific code is isolated behind adapters.
- Consumer fixtures import through package specifiers, never relative source paths.
See also: