# waboom — WhatsApp middleware & API (AI integration guide)

waboom is a hosted, multi-tenant middleware that exposes WhatsApp as an API. It turns
every incoming WhatsApp message into an HMAC-signed webhook you can route to your own
flows — Shopify Flow, n8n, Make, or any HTTP endpoint. Conversations are not archived:
recently-transited messages sit in a temporary in-memory window (24h or 10,000 messages per
instance, whichever comes first, cleared on restart); on disk only chat metadata (jid,
display name, last-message anchor, unread count) and instance metadata (with hashed tokens)
are kept. One exception to know about: if you configure a webhook, every delivery is
recorded in the database with the FULL event payload — message text included — for retry,
log and replay, and is pruned according to your plan's retention (1 to 90 days). A phone
number is connected by scanning a QR code (linked as a companion device).

Feed this document to an AI assistant to integrate with the REST API, stable under `/v1`.

- Website: `https://www.waboom.it/`
- Base URL: `https://api.waboom.it/v1`
- Machine-readable spec (OpenAPI 3.1): `https://api.waboom.it/openapi.json`
- Postman collection: `https://api.waboom.it/waboom.postman_collection.json`
- Docs: `https://www.waboom.it/docs`
- Short index for agents: `https://www.waboom.it/llms.txt`

Built by Barikreativa (`https://barikreativa.com`), an Italian software studio. Hosted in
the EU (Fly.io, Paris). Conversations are not persisted to disk (a temporary in-memory
window handles retries and reads); chat metadata and instance metadata (hashed tokens) are
stored on disk. Webhook delivery records DO contain the event payload (message text
included) for the duration of the plan's retention window.

## Pricing

- Free — €0/month: 1 WhatsApp number, 1,000 messages/month, basic webhooks.
- Starter — €19/month: 1 number, 10,000 messages/month, signed webhooks + retry, org API key.
- Pro — €49/month: 3 numbers, 50,000 messages/month, broadcast, idempotency, replay.
- Business — €149/month: 10 numbers, 250,000 messages/month, SLA 99.5%, GDPR export, custom webhook domain.
- Enterprise — custom (unlimited numbers, dedicated instance, DPA, SSO). Overage: €2 per 1,000 messages beyond the plan cap.

## Authentication

Every request sends `Authorization: Bearer <TOKEN>`. Three credential kinds:

- **Org API key** (`wbo_...`) — organization-scoped, role-based. Create it in the console
  (Impostazioni → API keys). It can create/list/delete instances in its org AND operate
  any instance in that org (status, QR, send, webhook, broadcast, …). Use this to run the
  **entire flow headless** from an external app — no dashboard needed.
- **Instance token** — scoped to one instance (`/v1/instances/{id}/*`). Optional; useful to
  hand a narrow, per-instance credential to a service. Revocable.
- **Admin key** — platform super-admin. Never expose client-side.

## Headless bootstrap (create an instance + pair, entirely via API)

With an **org API key** an external app does the whole flow, no panel:

```bash
# 1) Create an instance in your org
curl -X POST https://api.waboom.it/v1/instances \
  -H "Authorization: Bearer $ORG_KEY" -H "Content-Type: application/json" \
  -d '{"name":"cliente-acme"}'
# -> { "id": "<instance-id>", "token": "<instance-token>" }  (per-instance token, optional)

# 2) Fetch the pairing QR and show it to the user
curl https://api.waboom.it/v1/instances/$ID/qr -H "Authorization: Bearer $ORG_KEY"
# -> { "qr": "...", "imageDataUrl": "data:image/png;base64,..." }

# 3) Poll status until connected
curl https://api.waboom.it/v1/instances/$ID/status -H "Authorization: Bearer $ORG_KEY"
# -> { "connection": "open", "me": "39...@s.whatsapp.net", "hasCreds": true }

# 4) Send — the org key works on every instance route in the org
curl -X POST https://api.waboom.it/v1/instances/$ID/send/text \
  -H "Authorization: Bearer $ORG_KEY" -H "Content-Type: application/json" \
  -d '{"jid":"39333xxxxxxx@s.whatsapp.net","text":"Ciao"}'
```

### Account endpoints (org API key or admin key)
- `POST /v1/instances` — create an instance in the key's org → `{ id, token }`
- `GET  /v1/instances` — list the org's instances
- `DELETE /v1/instances/{id}` — delete an instance in the org

## Conventions

- JSON everywhere. Only send `Content-Type: application/json` when the request has a body
  (empty body with that header is rejected).
- `{id}` is an instance UUID. `jid` is a WhatsApp address: `39333xxxxxxx@s.whatsapp.net`
  (individuals) or `...@g.us` (groups).
- **Rate limiting** (anti-ban): sends are throttled per instance. Over the limit you get
  `429` with a `Retry-After` header (seconds) — back off and retry.
- **Idempotency**: on send endpoints pass `Idempotency-Key: <uuid>`. Retrying with the same
  key replays the original response instead of sending a duplicate message.

## Endpoints

### Send a text message
`POST /v1/instances/{id}/send/text`
Body: `{ "jid": string, "text": string, "quotedId"?: string }`
Response `200`: `{ "id": string | null }`

### Send media
`POST /v1/instances/{id}/send/media`
Body: `{ "jid": string, "type": "image"|"video"|"audio"|"document", "base64": string, "caption"?: string }`
Response `200`: `{ "id": string | null }`

### Connection status
`GET /v1/instances/{id}/status`
Response: `{ "connection": "connecting"|"qr"|"open"|"close", "me": string|null, "hasCreds": boolean }`

### Pairing QR
`GET /v1/instances/{id}/qr` → `{ "qr": string, "imageDataUrl": string }` (or `409` if already connected)
`POST /v1/instances/{id}/pair` — Body: `{ "phone": string }` (international digits). Returns `{ "code": string }` to enter on the phone (WhatsApp > Linked devices > Link with phone number). Alternative to QR; `409` if already paired.

### Webhook configuration
`GET /v1/instances/{id}/webhook` → `{ "url", "events", "secret" }`
`PUT /v1/instances/{id}/webhook`
Body: `{ "url": string|null, "events": string[], "headers"?: { [name]: string } }`. Optional `headers` are custom HTTP headers sent with every delivery (e.g. a receiver auth token); reserved `content-type`/`x-waboom-*` cannot be overridden (max 20).
Events: `message.received`, `message.status`, `message.reaction`, `message.edited`, `message.deleted`, `group.update`, `instance.state`, `call.received`, or `"*"`.
`400` if the URL is not a public https endpoint.

### Number check
`POST /v1/instances/{id}/check` — Body: `{ "numbers": string[] }` (max 100). Returns `{ "results": [{ "input", "exists", "jid" }] }`. Validate numbers before sending.

### Broadcast (bulk, paced)
`POST /v1/instances/{id}/broadcasts` — Body: `{ "text": string, "recipients": string[] }` (max 5000)
`GET  /v1/instances/{id}/broadcasts` — list
`GET  /v1/instances/{id}/broadcasts/{bid}` → `{ "status", "total", "sent", "failed" }`

### GDPR
`GET    /v1/instances/{id}/contacts/{jid}/export` → messages currently in the in-memory window for a contact
`DELETE /v1/instances/{id}/contacts/{jid}` → `{ "removed": number }`

The gateway does not keep conversations: it holds an in-memory window bounded to the last
24 hours or 10,000 messages per instance, whichever comes first (a busy instance can evict
messages well inside 24h). Deeper reads are requested from WhatsApp on demand, best effort
(at most 200 messages per request; a request that returns nothing puts that chat on a
60-second cooldown). Note that webhook delivery records keep the event payload, message
text included, until the plan's retention window prunes them.

## Receiving webhooks

waboom POSTs events to your configured URL with headers:

- `X-Waboom-Event`: event type
- `X-Waboom-Timestamp`: unix seconds
- `X-Waboom-Delivery`: delivery uuid
- `X-Waboom-Signature`: `sha256=<hex>`

Payload: `{ "event": string, "instanceId": string, "timestamp": number, "data": object }`

`data` by event:
- `message.received` — `{ id, chatJid, fromMe, sender, senderName, timestamp, type, text, hasMedia, mediaType, quotedId, replyId? }`. For catalog messages `type` is `"order"`/`"product"` and a `commerce` field is present: `{ orderId?, itemCount?, currency?, total?, token?, productId?, title? }`.
- `message.reaction` — `{ messageId, chatJid, emoji, from }` (empty `emoji` = reaction removed).
- `message.edited` — `{ kind: "edited", messageId, chatJid, text }` (new text).
- `message.deleted` — `{ kind: "deleted", messageId, chatJid }` (delete-for-everyone).
- `message.status` — `{ id, jid, status }`.
- `group.update` — `{ jid, action?, participants?, update? }`.

Verify the signature (Node):

```js
import crypto from 'node:crypto';
function verify(req, secret) {
  const sig = req.headers['x-waboom-signature'];      // "sha256=<hex>"
  const ts  = req.headers['x-waboom-timestamp'];
  const expected = 'sha256=' + crypto.createHmac('sha256', secret)
    .update(ts + '.' + req.rawBody)                    // rawBody = exact received body
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
```

Deliveries are queued, retried with exponential backoff, dead-lettered after max attempts,
and can be replayed from the console.

## Error codes

- `401` unauthorized — missing/invalid token
- `403` forbidden / insufficient_role
- `404` not_found
- `429` rate_limited — respect `Retry-After`
- `400` validation — e.g. `invalid_webhook`, `text_required`, `too_many_recipients`

## MCP server (AI agents)

Official MCP server `@waboom/mcp` connects waboom to Claude, Cursor and any MCP client.
Tools: `waboom_send_text`, `waboom_send_media`, `waboom_send_location`, `waboom_send_reaction`,
`waboom_check_numbers`, `waboom_mark_read`, `waboom_send_typing`, `waboom_list_chats`,
`waboom_get_messages`, `waboom_download_media`, `waboom_list_groups`, `waboom_get_status`.
Config via env: `WABOOM_TOKEN` (org API key or instance token), `WABOOM_INSTANCE_ID`, `WABOOM_BASE_URL` (optional).

```json
{ "mcpServers": { "waboom": { "command": "npx", "args": ["-y", "@waboom/mcp"],
  "env": { "WABOOM_TOKEN": "wbo_...", "WABOOM_INSTANCE_ID": "..." } } } }
```

## Example

```bash
curl -X POST https://api.waboom.it/v1/instances/$ID/send/text \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"jid":"39333xxxxxxx@s.whatsapp.net","text":"Hello from waboom"}'
```

## Templates, interactive messages & opt-out

Message templates with `{{variable}}` interpolation:
- `POST /v1/instances/{id}/send/template` — body `{ "templateId": "...", "jid": "...", "variables": { "nome": "Anna" } }`. Placeholders `{{nome}}` in the template body are replaced; unknown placeholders are left visible. Templates are created per organization (console → Template, or org-scoped API).

Interactive messages (rendering is best-effort over the unofficial connection and depends on the recipient's WhatsApp client):
- `POST /v1/instances/{id}/send/buttons` — body `{ "jid": "...", "text": "...", "footer": "...", "buttons": [{ "id": "yes", "text": "Sì" }] }`.
- `POST /v1/instances/{id}/send/list` — body `{ "jid": "...", "text": "...", "buttonText": "Apri", "sections": [{ "title": "...", "rows": [{ "id": "r1", "title": "Opzione 1", "description": "..." }] }] }`.
- The user's tap arrives as a `message.received` webhook event with a `replyId` field (the selected `id`).

Opt-out / consent (suppression):
- Contacts who send `STOP` (also: basta, cancellami, annulla, unsubscribe, …) are added to the instance opt-out list automatically.
- Sending to an opted-out contact returns `403 recipient_opted_out`; broadcasts skip them.
- `GET /v1/instances/{id}/optouts` — list. `POST /v1/instances/{id}/optouts` `{ "jid": "..." }` — add. `DELETE /v1/instances/{id}/optouts/{jid}` — remove (re-consent); `jid` is URL-encoded.
