# WhatsApp Founders — Health Network Integration Spec

> Machine- and human-readable spec for integrating a system into the WhatsApp
> Founders health network. Hand this whole file to a coding agent (Claude Code,
> Cursor, etc.) — it has everything needed to implement the integration.

## What this is

A status / outage-detection network for teams that build on the WhatsApp / Meta
Cloud API. The simplest integration is **failure-first**: wrap your Cloud API
calls in try/catch and POST one tiny request when something breaks — that's the
whole thing. Optionally, you can *also* send periodic **healthy heartbeats** (for
your own uptime % and a dead-man's-switch). In return you get: group-outage alerts
(signed webhooks), an anonymous cross-company benchmark (k-anonymity ≥ 5), and a
live status badge.

**Privacy:** the network never receives your messages or customer data — only
`up`/`fail` signals and optional aggregate error/total counts. You push; it never pulls.

## Credentials (fill these)

Get them at <https://www.whatsappfounders.com.br/ativar/> with your invite code.

| Name | Value | Notes |
|---|---|---|
| API base | `https://api.whatsappfounders.com.br` | |
| `WHF_KEY` | `whf_live_…` | public — identifies your tenant |
| `WHF_SECRET` | `whfsec_…` | **secret** — store in an env var, never in code/repo/front-end |
| `TENANT` | your tenant slug | same tenant the key belongs to |

## Reporting API

```
POST {API}/v1/ping/{TENANT}/{component}[/fail][?period=600]
Headers: WHF-Key: {WHF_KEY}
         WHF-Secret: {WHF_SECRET}
Body (optional, JSON): {"error_count": <int>, "total_count": <int>}
```

- **`/fail` suffix → error (`down`); no suffix → healthy (`up`).**
- **Two ways to report — pick what fits (you can mix per component):**
  - **Failure-first (recommended, lowest effort):** *omit* `?period`. POST to
    `…/{component}/fail` whenever you catch an error. The monitor goes `down` only
    on an explicit `/fail`, **auto-recovers ~30 min after the last error** (or
    instantly if you send one healthy `up`), and is **never** auto-flipped for
    silence. No cron, no periodic traffic, nothing to send while you're healthy.
  - **Heartbeat (optional):** add `?period=600` to declare a cadence and send a
    healthy ping every ~period. If pings then stop arriving, the monitor auto-flips
    to **silent/down** (dead-man's-switch) — use it to measure your own uptime % or
    to catch "my whole reporter died." Add `&grace=120` for a late-ping grace window.
- Optional body counts: an **error ratio `error_count/total_count` > 25%** is
  treated as a failure even on an `up` ping, and feeds the anonymous benchmark.
- **Components:** you report the four messaging ones (`envio`, `recebimento`,
  `midia`, `templates`); the network computes `qualidade-limites` and
  `plataforma-meta` — don't send those. Full per-component detail in **Components** below.
- **Responses:** `200 {"ok":true,"component":…,"status":"up|down"}` ·
  `401` bad credentials · `403` key not valid for this tenant ·
  `404` unknown component · `429` rate limit (max 120 requests/min/tenant).

### Failure-first (recommended) — report on error, nothing when healthy

Wrap your Cloud API calls; on an error, fire one request. No cron, no `?period`.

```bash
# in your send path, whenever a call to the Cloud API errors:
curl -fsS -X POST "$API/v1/ping/$TENANT/envio/fail" \
  -H "WHF-Key: $WHF_KEY" -H "WHF-Secret: $WHF_SECRET"
# (optional) clear it immediately on the next successful send:
curl -fsS -X POST "$API/v1/ping/$TENANT/envio" \
  -H "WHF-Key: $WHF_KEY" -H "WHF-Secret: $WHF_SECRET"
```

```
// pseudocode — fire-and-forget; never let the report throw or block your request
try   { await metaCloudApi.send(...) }
catch (e) { post(`${API}/v1/ping/${TENANT}/envio/fail`, whfHeaders) }  // don't await/raise
```

### Heartbeat (optional) — periodic healthy ping for uptime % / dead-man's-switch

```bash
# every ~5 min, healthy:
curl -fsS -X POST "$API/v1/ping/$TENANT/envio?period=600" \
  -H "WHF-Key: $WHF_KEY" -H "WHF-Secret: $WHF_SECRET"
# on a send failure instead:
curl -fsS -X POST "$API/v1/ping/$TENANT/envio/fail?period=600" \
  -H "WHF-Key: $WHF_KEY" -H "WHF-Secret: $WHF_SECRET"
```

### With per-component counts (for the benchmark)

```bash
curl -fsS -X POST "$API/v1/ping/$TENANT/envio/fail" \
  -H "WHF-Key: $WHF_KEY" -H "WHF-Secret: $WHF_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"error_count": 3, "total_count": 250}'
```

### Reliability, retries & errors

- **Idempotent within a window:** the network keeps the *latest* signal per
  monitor per ~4-min bucket (last-write-wins), so retrying a timed-out POST is safe.
- **Resilience:** use a short timeout and never let a failed report crash your
  app; log non-2xx. A `200 {"ok":true,...}` means the signal was recorded.
- **Errors** return a stable envelope:
  `{"error":{"code":"…","message":"…","status":<int>}}` —
  `401` bad credentials · `403` key/tenant mismatch · `404` unknown component ·
  `429` rate limit (120 req/min/tenant). On `429`, back off and retry after a few
  seconds (no `Retry-After` header is sent).
- **Confirming it works:** a `200` means recorded. To see your **own** live state,
  call `GET {API}/v1/me` (same WHF-Key/WHF-Secret) — see below. Your signal also
  feeds the group `/status/` page and the anonymous benchmark (per-component rates
  appear once ≥ 5 companies report).

### Check your own state — `GET /v1/me`

The "did my report land?" self-check. Returns **only your** monitors
(tenant-scoped), so you can verify each one is being received:

```bash
curl -fsS "$API/v1/me" -H "WHF-Key: $WHF_KEY" -H "WHF-Secret: $WHF_SECRET"
```
```json
{
  "tenant": "acme",
  "monitors": [
    {"component": "envio", "name": "default", "status": "up",
     "last_ping_at": "2026-06-28T16:10:21Z", "period_seconds": null, "seconds_since_ping": 1}
  ],
  "checked_at": "2026-06-28T16:10:22Z"
}
```
`status` ∈ `new` (no signal yet) · `up` · `grace` (a little late, heartbeat mode) ·
`down` (last signal was `/fail`, or — in heartbeat mode — silent past period+grace).
A `period_seconds` of `null` means the monitor is in **failure-first mode** (no
dead-man's-switch). Use `seconds_since_ping` to confirm your reporter is firing.

### Multiple numbers / named monitors

Running more than one WhatsApp number or pipeline? Give each its own **named
monitor** so they're tracked independently:

```
POST {API}/v1/ping/{TENANT}/{component}/{name}[/fail][?period=600]
```

`{name}` is a stable slug you choose (e.g. `disparos-sp`); the simple path uses
`default`. Example: `POST {API}/v1/ping/acme/envio/disparos-sp/fail`.

### Job-start marker (optional)

`POST {API}/v1/ping/{TENANT}/{component}/start` marks the start of a run (for
duration tracking); it doesn't change up/fail counts.

## Components

Six components make up the group's health. **You report the four messaging ones**
by POSTing `/fail` when you detect a problem (optionally `up`/heartbeats too); the
network computes the other two — do **not** send them. `/fail` on error, no suffix
for healthy, or pass `{"error_count","total_count"}` and an error ratio > 25% counts
as a failure.

### Components you report

Start with `envio` — it's the signal that matters most. Add the others only if you
can actually measure them.

| Component | Measures | Send `/fail` when… | (Optional) send `up` when… | How to detect in your stack |
|---|---|---|---|---|
| `envio` | You can **send** messages via the Cloud API. | sends are erroring — HTTP 4xx/5xx from `/messages`, or send error codes (e.g. `131xxx`, `368`, `80007`, `130472`) | your recent `POST /{phone_id}/messages` calls returned 200 / `messages` accepted | inspect the result of every outbound send in the last window; fail if the error ratio crosses your threshold |
| `recebimento` | Inbound **webhooks** from Meta are reaching your backend. | you have evidence inbound stopped while your webhook endpoint is up (e.g. a known sender's reply never arrives) | you received inbound `messages` webhooks recently (or the window was simply quiet — no traffic expected) | count inbound `messages` webhook events; a synthetic self-message round-trip is the strongest check |
| `midia` | **Media** send/receive (image, document, audio, video, sticker). | media upload/download/send fails while text works — e.g. `131052` (media too large), `131053` (unsupported), download/`/media` errors | media messages went through | track media-typed sends + media download calls separately from text |
| `templates` | **Template (HSM)** sends — approval + delivery. | template sends error — `132xxx` (rejected / paused / per-template limit) or `131026` (undeliverable) | template sends were accepted | track the result of `type:"template"` sends specifically |

**Tip:** measuring `envio`/`midia`/`templates` from your own send results is exact.
For `recebimento`, "no inbound" usually means "quiet", not "broken" — only send
`/fail` when you have positive evidence inbound is down, to avoid false alarms.

### Components the network computes (do NOT send)

- **`qualidade-limites`** — your WhatsApp account's **quality rating**
  (`GREEN`/`YELLOW`/`RED`) and **messaging tier**. The network derives this from the
  group's fleet of numbers (`health_status.can_send_message` + `quality_rating`).
  Note: `TIER_1K` / `can_send_message:"LIMITED"` is a **normal capacity tier, not a
  failure** — only `YELLOW`/`RED` quality degrades it. A member pinging this is
  ignored (or pollutes the shared signal) — leave it to the network.
- **`plataforma-meta`** — health of the **Meta / Cloud API platform itself**. The
  network probes Meta's Graph API and watches Meta's official status feed
  (metastatus RSS) every minute, so a real platform incident is confirmed by active
  probes, not self-reported. Self-reporting this is ignored by detection.

## Webhooks (optional — react to outages automatically)

Register a receiver URL at <https://www.whatsappfounders.com.br/alertas/>. It
returns a **webhook secret** (different from `WHF_SECRET`). Every delivery:

```
Headers: X-WHF-Event:     incident.opened | incident.resolved
         X-WHF-Signature: sha256=<hex hmac_sha256(webhook_secret, "{timestamp}.{raw_body}")>
         X-WHF-Timestamp: <unix>
Body (CloudEvents JSON):
  {"event","component","status","scope","confidence","contributing_companies","incident_id","occurred_at"}
```

- `scope` ∈ `meta_platform` (Meta is down) · `group` (the group is down) · `tenant` (only you).
- **Verify before trusting:** read the unix time from the **`X-WHF-Timestamp`**
  header (call it `t`); reject if `|now - t| > 300s`; recompute
  `hmac_sha256(webhook_secret, "{t}.{raw_body}")` over the **raw** request body
  (before JSON parsing); strip the `sha256=` prefix from `X-WHF-Signature` and
  compare the hex **constant-time**.

### Delivery semantics

We POST the JSON to your URL and expect a **2xx within 5 s**. On a timeout,
connection error, or 5xx we make **up to 3 delivery attempts total** (1 + 2 retries) with backoff; after **10
consecutive failures** the subscription **auto-disables** (re-register at
`/alertas/` to re-enable). Delivery is **at-least-once** with **no ordering
guarantee** — make your handler **idempotent on `incident_id`** and return 2xx
fast (do any slow work async).

### Body fields

| Field | Type | Meaning |
|---|---|---|
| `event` | string | `incident.opened` or `incident.resolved` |
| `component` | string | the affected component (`envio`, `recebimento`, …) |
| `status` | string | `major_outage` (opened) / `operational` (resolved) |
| `scope` | string | `meta_platform` · `group` · `tenant` |
| `confidence` | float (0–1) | detection confidence |
| `contributing_companies` | int | distinct companies in the signal |
| `incident_id` | string (uuid) | stable incident id — **dedupe on this** |
| `occurred_at` | string | ISO-8601 UTC timestamp |

## Incident feed (RSS)

A public, no-auth RSS feed of recent incidents — drop it into a Slack RSS app or
any reader for a zero-code subscription:

```
{API}/v1/feed.rss
```

## Status badge

```
https://img.shields.io/endpoint?url={API}/v1/badge/envio
```

## Versioning & support

- The API is versioned under `/v1` and `v1` is **stable** — breaking changes ship
  under a new version with notice; your integration won't break under you.
- Questions or integration issues: reach out in the **WhatsApp Founders** community.

## Task for the agent

1. **Wrap the WhatsApp / Cloud API calls in try/catch.** On an error talking to
   `envio` (and, if measurable, `recebimento`/`midia`/`templates`), fire a one-off
   `POST {API}/v1/ping/{TENANT}/{component}/fail` — **fire-and-forget**: never let
   the report throw, await, or block the request path. This alone is a complete
   integration (no cron, no `?period`).
2. (Optional) On the next success, POST the same path **without** `/fail` to clear
   immediately; otherwise the monitor auto-recovers ~30 min after the last error.
3. (Optional — only if they want their own uptime % / a dead-man's-switch) add a
   scheduled job that POSTs a healthy heartbeat with `?period=600` every ~5 min, and
   `…/fail?period=600` when the send path is failing.
4. Read `WHF_KEY` / `WHF_SECRET` from **environment variables** — never hardcode the secret.
5. Make every call resilient: short timeout, never crash the app on a failed report, log non-200s.
6. (Optional) Add a signed webhook receiver per the **Webhooks** section
   (verify the signature; dedupe on `incident_id`; return 2xx fast) and wire a
   reaction (pause campaigns / failover / page on-call) for `incident.opened` with
   scope `group`/`meta_platform`, undone on `incident.resolved`.

Ask the user which components they can measure and what reaction they want on an
outage, then implement it in their project's language and conventions.
