wats.sh
Capability status

Live-testing campaign

How WATS gets validated against live Meta assets, and the redacted run log.

active · reviewed 2026-06-10

WATS development is credential-free by default: MockTransport, synthetic webhooks, no Meta calls in CI. A live campaign is the exception — an operator-authorized run against real Meta assets. This page is the runbook plus the redacted execution log.

Scope

Included:

  • Verify Meta accepts WATS request shapes for implemented surfaces.
  • Verify response shapes against current WATS types.
  • Observe webhook/status side effects.
  • Record redacted evidence and cleanup state.

Excluded by default:

  • Admin mutations beyond explicitly authorized sandbox resources.
  • Production WABA/phone/catalog/template/Flow changes.
  • Phone registration/deregistration, callback URL overrides, OAuth/token exchange, publish operations.
  • Anything not guarded by WATS_LIVE_ENABLE=1 plus a domain-specific opt-in flag for mutations.

Credential inventory

Baseline:

WATS_LIVE_ENABLE=1
WATS_GRAPH_BASE_URL=https://graph.facebook.com
WATS_GRAPH_API_VERSION=v25.0
WATS_ACCESS_TOKEN=<redacted>
WATS_WABA_ID=<redacted>
WATS_PHONE_NUMBER_ID=<redacted>
WATS_TEST_RECIPIENT_E164=<redacted>
WATS_TEST_RUN_ID=<generated-per-run>
WATS_TEST_RESOURCE_PREFIX=wats-live-<run-id>

Webhook:

WATS_APP_SECRET=<redacted>
WATS_VERIFY_TOKEN=<redacted>
WATS_PUBLIC_WEBHOOK_URL=<redacted-or-test-url>
WATS_WEBHOOK_PATH=/webhook

Per-domain opt-in flags gate mutations: WATS_ENABLE_TEMPLATE_MUTATIONS=1, WATS_ENABLE_FLOW_MUTATIONS=1, WATS_ENABLE_FLOW_PUBLISH=1, WATS_ENABLE_CALLING_LIVE=1, WATS_ENABLE_CALL_ACCEPT_WEBRTC=1, WATS_ENABLE_COMMERCE_MESSAGES=1. Media runs also set fixture paths and WATS_MAX_UPLOAD_BYTES / WATS_MAX_DOWNLOAD_BYTES caps. Deferred admin mutations need a separate campaign with their own flags (WATS_ENABLE_ADMIN_MUTATIONS=1, WATS_ENABLE_DESTRUCTIVE=1).

Rules:

  • Secrets stay in env vars or @wats/config env-secret refs.
  • No raw secrets in docs, commits, issues, or chat.
  • WATS_LIVE_ENABLE=1 is necessary but not sufficient for mutations; each domain flag must also be set.

Phase order

Read-only before side-effecting before destructive.

  1. Authorization and dry run. Confirm explicit operator authorization for the exact assets and phases. Confirm every asset is sandbox/test-safe. Generate the run id and resource prefix. Run local tests and typecheck. Validate config without resolving secret values.
  2. Read-only discovery. getWabaInfo, listSubscribedApps, listPhoneNumbers, getPhoneNumberInfo, getPhoneNumberSettings({ includeSipCredentials: false }), getBusinessProfile, getCommerceSettings, listMessageTemplates, getMessageTemplate, listFlows, getFlow, getFlowAssets, and media metadata reads with strict maxBytes.
  3. Webhook passive validation. Stand up a webhook behind an authorized public URL. Verify the GET challenge, verify a locally signed POST, trigger one inbound test message, confirm normalizer/router/listener behavior. No callback URL overrides.
  4. Low-impact sends. Text to the test recipient, observe the status webhook, mark-as-read and typing indicators on test messages only, then location/contacts/reactions/interactive bodies and an existing approved template. Catalog/product sends only with test catalog assets.
  5. Media lifecycle. Upload a small fixture, resolve metadata, download bytes with maxBytes and integrity checks, send by media id, delete only media created by this run, record cleanup in the manifest.
  6. Template mutations (flag-gated). Create a uniquely prefixed template, list/get/update it within Meta's review rules, delete only what this run created. Never touch production templates.
  7. Flow draft management (flag-gated). Create/read/update a uniquely prefixed draft. Publish only with the publish flag and explicit acceptance of the state-transition risk. Delete only this run's drafts; treat deprecate as irreversible.

Calling operator facts from Meta docs before any live attempt:

  • Supported media/signaling modes are Graph APIs + webhooks + WebRTC, SIP + WebRTC, and SIP + SDES SRTP.
  • SDP examples include OPUS and G.711 codecs (PCMA, PCMU); WATS validates and forwards SDP but does not negotiate media.
  • Business-initiated calling is not available for numbers from USA, Canada, Egypt, Vietnam, Nigeria.
  • Production access is platform-gated: App Review, WhatsApp Business Messaging permission, and calling eligibility, normally including a 2,000 daily messaging limit.
  • Sandbox flows are limited and may require Tech Partner access.
  • WATS has no SIP server implementation and cannot provision calling access.
  1. Calling (flag-gated). Confirm the phone is calling-enabled, initiate a minimal test call, reject or terminate quickly. Accept/WebRTC flows need their own flag and separate confirmation.
  2. Deferred admin mutations. Separate future campaign: callback overrides, public key mutation, profile/settings/commerce writes, registration, token flows, QR codes, block/unblock.
  3. Cleanup and final verification. Execute the cleanup plan, re-run read-only discovery for touched families, produce a sanitized report, list anything needing manual cleanup.

Risk classification

ClassHelpers
Read-only firstgetWabaInfo, listSubscribedApps, listPhoneNumbers, getPhoneNumberInfo, getPhoneNumberSettings, getBusinessProfile, getCommerceSettings, listMessageTemplates, getMessageTemplate, listFlows, getFlow, getFlowAssets, downloadMedia, downloadMediaBytes with caps, getUploadSession, webhook GET verify
Side-effecting but controlledall send* composers, markMessageAsRead, indicateTyping, uploadMedia, upload sessions, createMessageTemplate, updateMessageTemplate, createFlow, updateFlowMetadata, updateFlowJson, initiateCall, preAcceptCall, acceptCall, rejectCall, terminateCall
Destructive / cleanup-onlydeleteMedia, deleteMessageTemplate, deleteFlow, deprecateFlow, publishFlow, admin mutations

getPhoneNumberSettings({ includeSipCredentials: true }) may return SIP credentials. Do not run it unless the campaign requires it, and never log the raw response.

Redaction rules

Always redact: Authorization headers and Bearer tokens, access tokens, app secrets, verify tokens, service bearer tokens, x-hub-signature-256 values, challenge values, raw env values.

Mask or hash: WABA/phone-number/app/business ids, recipient numbers and wa_id values, message ids, media ids and URLs, upload session ids, production-linked template ids/names, Flow ids, call ids, catalog/product ids.

Sensitive response fields: SIP credentials, production-linked business profile contact details, signed media URLs (redact at least the query string), webhook payload PII.

Logging: record method, endpoint group, status, and Graph code/subcode/classification. No full URLs with query strings. Correlate via salted hashes (sha256(value + runSalt).slice(0, 12)). Raw response capture requires a separate opt-in and lives outside the repository.

Cleanup and rollback

Before any mutation, write a run manifest outside the repository (/tmp/wats-live-${WATS_TEST_RUN_ID}.json) recording resource type, id/name, creation timestamp, endpoint, cleanup action, and cleanup status.

Cleanup order: terminate test calls, delete this run's media, delete this run's templates where Meta allows, delete this run's draft Flows, restore any changed settings, stop the webhook tunnel, re-run read-only inventory, rotate the live token if policy requires, produce the sanitized report.

Rollback limits: sent messages, read receipts, typing indicators, and calls cannot be unsent. Published/deprecated Flows may not be reversible. Template review/status changes may persist. Use only test recipients and uniquely prefixed resources.

Abort criteria

Abort immediately if:

  • Any asset or recipient is not the intended test asset.
  • Token scope is wrong or unexpectedly broad.
  • Meta returns rate-limit, account-warning, integrity, or policy errors beyond the expected test condition.
  • Cleanup fails for a created resource.
  • A raw secret or PII value appears in logs.
  • A side-effecting phase is reached without its opt-in flag.

Harness requirements

Live harnesses stay outside normal bun test unless WATS_LIVE_ENABLE=1 is set, require domain mutation flags for side-effecting tests, require the run id and resource prefix, default to dry-run/MockTransport when flags are absent, write manifests outside the repository, redact before printing, and keep CI and default package scripts credential-free.

Execution log

Redacted, non-secret summaries of authorized runs. These entries are the evidence behind the live-validated tags in the parity matrix.

2026-05-29 — Railway container, outbound + status webhooks

  • Environment: WATS deployed as a container on Railway, live mode, public HTTPS callback. Explicit operator authorization for a disposable test number; token deactivated after the run.
  • Outbound text and approved-template sends returned HTTP 200 with real Meta message ids.
  • Meta delivered status webhooks to the service; the service logged normalized kind="status" updates (ids only, no PII), confirming HMAC verification, normalization, router dispatch, and acknowledgement end to end.
  • Result: outbound send and inbound status webhook delivery live-validated for the test asset. Inbound message and richer families left for the next run.

2026-06-10 — env-gated harness against live Meta (v0.3.25)

  • Environment: the env-gated probe harness (packages/testing/live/), driven so secrets arrived only as subprocess env. Live flag plus per-domain mutation flags. Explicit operator authorization for the disposable test asset; redacted ledgers written outside the repo.
  • Read-only discovery (8/8 pass): getWabaInfo, listSubscribedApps, listPhoneNumbers, getPhoneNumberInfo, getBusinessProfile, getCommerceSettings, listMessageTemplates, listFlows — all parsed live Graph v25.0 responses into current WATS types. Commerce settings and Flows returned empty on this asset; Flow mutations not exercised.
  • Sends: text send accepted (HTTP 200 + message id); its status webhook reported failed — expected, since freeform text needs an open 24h customer window. An approved-template send produced the full sent → delivered → read status chain plus an inbound reaction message, exercising the webhook runtime (HMAC → normalize → router) for both status and message families.
  • Media lifecycle: uploadMedia, downloadMedia metadata, sendImage by media id, and deleteMedia cleanup all passed; zero leaked assets. One real wire-contract bug found: the integrity check on downloadMediaBytes accepted only base64 digests while Meta returns 64-char hex. Fixed same day; download without the integrity option had worked throughout.
  • Groups: listGroups read passed (200, empty). createGroup was blocked by Meta #131215 "This phone number is not eligible to access Groups APIs" — an asset entitlement limitation, not a WATS defect; the structured code surfaced through the service diagnostics. Groups mutations stay shape-only until a Groups-entitled test number exists.
  • Cleanup: media deleted, no group created, no leaked assets.

On this page