wats.sh
Guides

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 delivered or read from send success. Those states require observed webhook/event-store evidence.

Config template starting points:

  • examples/config/wats.config.example.yaml
  • examples/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

  1. Read the example index in examples/README.md.
  2. Inspect examples/config/wats.config.example.yaml or examples/config/wats.config.example.json.
  3. Inspect .env.example to see the expected env names without real values.
  4. Keep offline examples on MockTransport and synthetic webhook payloads/envelopes.
  5. Use the public docs and package imports only; do not import from packages/*/src in community examples.

Useful local checks:

bun test packages/testing/tests/wats52-community-examples.test.ts
bun run docs:check

For 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 demo

It 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.yaml and .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 Transport to 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 createWatsServiceOpenApiDocument or app.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.

On this page