Community examples
Offline-by-default examples for learning the public WATS package APIs without live WhatsApp credentials.
active — examples are offline by default · reviewed 2026-06-12
You can learn every public WATS package API without a live Meta app, a real WABA, a phone-number id, a webhook secret, a service bearer value, or a database URL. Every checked-in example runs offline: MockTransport for Graph-facing calls, synthetic webhook payloads for everything inbound.
Example safety contract
- Use MockTransport for Graph-facing calls.
- Use synthetic webhook payloads/envelopes for webhook/router examples.
- Keep checked-in configs credential-free; placeholder env names only.
- Do not commit raw access tokens, bearer tokens, webhook values, app values, live WABA ids, live phone-number ids, or database URLs.
- Webhook tunnels and live Meta callbacks are credential-gated guidance; docs tests and default example commands never run them.
- Do not infer WhatsApp
deliveredorreadfrom send success. Those states require observed webhook/event-store evidence.
Config template starting points:
examples/config/wats.config.example.yamlexamples/config/wats.config.example.json.env.example
Copy those files into ignored local paths before filling any real values. The checked-in files stay placeholders.
Quickstart flow with checked templates
- Read the example index in
examples/README.md. - Inspect
examples/config/wats.config.example.yamlorexamples/config/wats.config.example.json. - Inspect
.env.exampleto see the expected env names without real values. - Keep offline examples on MockTransport and synthetic webhook payloads/envelopes.
- Use the public docs and package imports only; do not import from
packages/*/srcin community examples.
Useful local checks:
bun test packages/testing/tests/wats52-community-examples.test.ts
bun run docs:checkFor no-network process smoke checks, use the dry-run
wats serve --config <path> --dry-run flow in the
CLI reference. The
examples below call createWatsServiceApp(...).fetch(request) directly when a
test wants a pure Request-to-Response fixture instead of a process wrapper.
Offline MockTransport bot example
Construct public WATS objects, inject MockTransport, and drive behavior with fixture data:
import { GraphClient } from "@wats/graph";
import { createMockTransport } from "@wats/graph/testing";
const mock = createMockTransport({
defaultResponse: {
status: 200,
body: { messages: [{ id: "wamid.OFFLINE_EXAMPLE" }] }
}
});
const client = new GraphClient({
accessToken: process.env.WATS_ACCESS_TOKEN ?? "offline",
apiVersion: "v25.0",
baseUrl: "https://graph.test/",
transport: mock.transport
});
await client.request({ method: "GET", path: "/me" });
console.log(mock.requests.length);The "offline" fallback string is a fixture value used only with
MockTransport. For credential-gated live testing, load env values from
ignored local files instead.
For webhook examples, use synthetic webhook payloads/envelopes with
normalizeWebhookEnvelope or a WebhookAdapter in a local test. Keep fixture
sender ids and message ids clearly synthetic.
Runnable minimal bot
The runnable examples/minimal-bot package is the 60-second offline onramp:
bun run --cwd examples/minimal-bot demoIt creates a createWatsServiceApp app, injects MockTransport, sends one text
message through the local service API, records a template intent without a
live template send, and calls normalizeWebhookEnvelope(...) on one synthetic
webhook envelope. No live Meta credentials required.
Service app fetch and OpenAPI example
@wats/service exposes a runtime-neutral Request-to-Response app. Use it
directly for local examples; no server process required.
import type { WatsProfileConfig } from "@wats/config";
import { createMockTransport } from "@wats/graph/testing";
import { createWatsServiceApp, createWatsServiceOpenApiDocument } from "@wats/service";
const profile: WatsProfileConfig = {
graph: { apiVersion: "v25.0", baseUrl: "https://graph.test/" },
whatsapp: { wabaId: "000000000000000", phoneNumberId: "00000000000" },
auth: { accessToken: { env: "WATS_ACCESS_TOKEN" } },
webhook: {
path: "/webhooks/whatsapp",
verifyToken: { env: "WATS_VERIFY_TOKEN" },
appSecret: { env: "WATS_APP_SECRET" },
maxBodyBytes: 1048576
},
service: {
host: "127.0.0.1",
port: 8787,
apiPrefix: "/api",
bearerToken: { env: "WATS_SERVICE_TOKEN" }
}
};
const mock = createMockTransport({
defaultResponse: { status: 200, body: { messages: [{ id: "wamid.OFFLINE_SERVICE" }] } }
});
const app = createWatsServiceApp({
profile,
secrets: {
accessToken: process.env.WATS_ACCESS_TOKEN ?? "offline",
webhookVerifyToken: process.env.WATS_VERIFY_TOKEN ?? "offline",
webhookAppSecret: process.env.WATS_APP_SECRET ?? "offline",
serviceBearerToken: process.env.WATS_SERVICE_TOKEN ?? "offline"
},
transport: mock.transport
});
const health = await app.fetch(new Request("https://local.test/healthz"));
const openapi = createWatsServiceOpenApiDocument(profile, { serverUrl: "https://local.test" });
console.log(health.status, openapi.openapi);This is a fetch/OpenAPI example, not a deployment recipe. It performs no live Meta calls because Graph access routes through MockTransport.
Webhook tunnel checklist (credential-gated)
Live tunnel work needs explicit operator setup and never runs in default tests. When you get there:
- Start from ignored local copies of
examples/config/wats.config.example.yamland.env.example. - Fill real values only in ignored local files or a secret manager.
- Choose a tunnel provider and map it to the configured webhook path.
- Register the callback URL and verify token in the Meta app dashboard.
- Confirm signature verification locally with the configured app value.
- Capture only redacted logs; never paste raw webhook bodies that contain user data into issues.
- Record delivered or read state only when an observed webhook/event-store record proves it.
Extensibility seams
Examples should build on the existing public seams:
- Transport and interceptors: inject MockTransport or a custom public
Transportto test Graph behavior without network calls. - TypedRouter, filters, and listeners: route normalized updates with public
TypedRouter,filtersTyped, and listener registries. - WebhookAdapter: adapt Request objects to normalized webhook dispatch while keeping signature and challenge verification at the edge.
- Config/service boundaries: parse and validate config outside the service, resolve secret env refs outside checked-in examples, and pass in-memory values to
createWatsServiceApp. - OpenAPI boundary: call
createWatsServiceOpenApiDocumentorapp.fetch(new Request("https://local.test/openapi.json"))for local documentation checks.
What is not here yet
Dockerfiles, Compose files, release automation, image publication, production hosting, and a full community gallery remain outside this set.