# whathooks — WhatsApp automation on your own number > whathooks turns a WhatsApp number into an automated channel: AI agents > reply in your voice, visual flows route every message, humans take over > from their own WhatsApp, and everything is also available as a signed > webhook + REST API. Numbers connect by QR scan; no WhatsApp Business API > approval process. This file is the complete integration reference, > written for AI coding agents and humans alike. > Machine-readable pricing: https://www.whathooks.app/pricing.md Base URL (production): `https://api.whathooks.app/v1` Web dashboard: `https://www.whathooks.app` ## Authentication Two credential types: - **API key** — for programmatic access. Send as `X-API-Key: wh_live_...` (an `Authorization: Bearer wh_live_...` header also works). Create keys in the dashboard under API Keys. API keys are scoped to one organization. - **JWT** — used by the dashboard (`POST /v1/auth/login`). Programmatic integrations should prefer API keys. ## Core flow 1. Create a session (one session = one WhatsApp number). 2. Poll the session until `status` is `QR`, render `qrDataUrl` for the user to scan with WhatsApp (Settings → Linked Devices → Link a Device). 3. When `status` becomes `CONNECTED`, the number is live. 4. Register a webhook URL to receive events. 5. Send messages with `POST /v1/messages`. ## Sessions Session statuses: `PENDING` (created, not initialized) → `QR` (code ready to scan) → `CONNECTING` (pairing accepted) → `CONNECTED` (online). Also `DISCONNECTED` (dropped; auto-reconnects) and `LOGGED_OUT` (device unlinked; new QR scan required). ### Create a session ```bash curl -X POST https://api.whathooks.app/v1/sessions \ -H "X-API-Key: wh_live_..." \ -H "Content-Type: application/json" \ -d '{ "label": "customer-42" }' # → { "id": "sess_8f2k19a7", "status": "PENDING", ... } ``` ### Poll a session / embed the QR ```bash curl https://api.whathooks.app/v1/sessions/sess_8f2k19a7 \ -H "X-API-Key: wh_live_..." # → { "status": "QR", "qrDataUrl": "data:image/png;base64,...", ... } ``` `qrDataUrl` is a ready-to-embed PNG data URI (``). QR codes rotate — keep polling while status is `QR` (or subscribe to `session.qr` webhooks) and swap in the fresh code. Dismiss the QR when the status becomes `CONNECTED`. This lets you onboard end users entirely inside your own product. ## Webhooks Register an HTTPS endpoint (dashboard → Webhooks, or `POST /v1/webhooks`). Events: - `message.received` — an inbound WhatsApp message arrived. - `session.status` — a session changed status. - `session.qr` — a new QR code was generated. - `contact.created` — a contact was saved (session auto-save, a flow's "Save contact" node, or manual creation in the dashboard). - `contact.updated` — a contact changed (fields edited, name backfilled from WhatsApp, or a new session linked). Webhooks can be scoped to one session or receive events from all sessions. ### Delivery headers - `X-Whathooks-Event` — event type, e.g. `message.received` - `X-Whathooks-Signature` — HMAC-SHA256 of the raw body, `sha256=` - `X-Whathooks-Delivery` — unique ID for the delivery attempt ### Payload envelope Every delivery shares `{ event, sessionId, data, timestamp }`: ```json { "event": "message.received", "sessionId": "sess_8f2a...", "data": { "id": "msg_3c91...", "conversationId": "conv_9d41...", "sessionId": "sess_8f2a...", "from": "15551234567", "isGroup": false, "participant": null, "mentionedMe": false, "pushName": "Jane Doe", "type": "TEXT", "text": "Hi there!", "media": null, "waMessageId": "3EB0A1B2C3D4E5F6", "timestamp": 1784892795 }, "timestamp": "2026-07-24T11:33:15.401Z" } ``` Field notes: - `type` is one of `TEXT`, `IMAGE`, `AUDIO`, `VIDEO`, `DOCUMENT`, `STICKER`, `LOCATION`, `CONTACT`, `UNKNOWN` (uppercase). - `data.timestamp` is unix **seconds** (the WhatsApp message time); the envelope `timestamp` is an ISO string (the delivery time). - `media` is `null` for text messages. For media messages it is `{ "url": "...", "mimeType": "image/jpeg", "fileName": "photo.jpg" }` — `fileName` may be null. `url` is a signed download link that expires after **1 hour**; download promptly (or re-fetch the message via `GET /v1/messages`, which returns a freshly signed link). - `text` carries the caption for media messages, and `null` when there is neither text nor caption. Group messages: `from` is the group JID (ends in `@g.us`), `isGroup` is `true`, `participant` is the JID of the group member who wrote the message, and `mentionedMe` is `true` when the connected number was @mentioned. To reply into the same group, POST `/v1/messages` with `to` set to the group JID from `from` verbatim. A bot that should only answer when addressed in groups can gate on `!data.isGroup || data.mentionedMe`. ### Signature verification (Node.js) Compute HMAC-SHA256 of the RAW request body with your webhook secret and compare it to `X-Whathooks-Signature` using a constant-time comparison: ```js import crypto from "crypto"; // Mount with the raw body, e.g. express.raw({ type: "application/json" }) function verifySignature(rawBody, header, secret) { const expected = crypto .createHmac("sha256", secret) .update(rawBody) .digest("hex"); const received = (header || "").replace(/^sha256=/, ""); const a = Buffer.from(received, "hex"); const b = Buffer.from(expected, "hex"); return a.length === b.length && crypto.timingSafeEqual(a, b); } ``` The webhook secret is shown once when the webhook is created. ### Payload mapping (optional) A webhook can carry `payloadMapping` rules that reshape `data` before delivery, keyed per event (`{ "message.received": [rules], "contact.created": [rules] }`; events without rules deliver their full payload): rename fields (`source` dot paths like `data.from`, also reaching `event`, `sessionId`, `timestamp`), format dates (`dateFormat`: `iso`, `unix`, `unix_ms`, or a UTC pattern like `yyyy-MM-dd HH:mm`), and inject fixed values (`value`). With rules set, `data` contains ONLY mapped fields; the envelope stays the same. ```json [ { "target": "phone", "source": "data.from" }, { "target": "message", "source": "data.text" }, { "target": "receivedAt", "source": "data.timestamp", "dateFormat": "yyyy-MM-dd HH:mm" }, { "target": "origin", "value": "whathooks" } ] ``` ## Sending messages ```bash curl -X POST https://api.whathooks.app/v1/messages \ -H "X-API-Key: wh_live_..." \ -H "Content-Type: application/json" \ -d '{ "sessionId": "sess_8f2a...", "to": "15551234567", "text": "Hello" }' # → { "id": "msg_5d72...", "waMessageId": "...", "status": "sent" } ``` - `to` accepts a bare phone number (digits only, with country code, no `+`) or a full WhatsApp JID. - The target session must be `CONNECTED`, otherwise the request returns 400. - Cold outreach is restricted by WhatsApp's anti-spam layer: the first message to a number that has NEVER messaged this session may be silently rejected server-side (the message is marked FAILED after the async ack). Numbers that already messaged you always work; for new contacts, have them text you first or start the chat from the session's phone. ## Mirror Links (lead protection) Mirror Link hides your leads' phone numbers from the people who answer them. When a lead DMs a mirrored session, whathooks creates a private WhatsApp group containing only your **human agent** and forwards the lead's messages there, prefixed with the lead's display name. When the human agent replies in the group, the reply is relayed to the lead as a normal chat from the session's number. Media relays in both directions. Groups are named ` #N` (default prefix `🔒 Lead`); the mapping is by group id, so renaming groups is safe. Relayed messages count toward the monthly message quota. Human agents are your org's directory of people who answer chats (as opposed to AI agents). A mirror link ties one session to one human agent. Managing either requires an admin role (or an API key). ```bash # 1. Create a human agent curl -X POST https://api.whathooks.app/v1/human-agents \ -H "X-API-Key: wh_live_..." -H "Content-Type: application/json" \ -d '{ "name": "Juan Pérez", "phoneNumber": "5491155551234" }' # → { "id": "ha_...", ... } # Also: GET /v1/human-agents · PATCH /v1/human-agents/:id (renumbering # propagates to that agent's links) · DELETE /v1/human-agents/:id # 2. Mirror a session to that human agent curl -X POST https://api.whathooks.app/v1/mirror-links \ -H "X-API-Key: wh_live_..." -H "Content-Type: application/json" \ -d '{ "sessionId": "sess_...", "humanAgentId": "ha_...", "groupPrefix": "ConsultasWeb" }' # groupPrefix optional (1-40 chars) → groups "ConsultasWeb #1", "ConsultasWeb #2", … # showLeadName optional (default true) → prefix relayed messages with the # lead's display name in bold; false uses a generic bold "Lead:" prefix # Both also patchable: PATCH /v1/mirror-links/:id { "showLeadName": false } # Pause / resume: PATCH /v1/mirror-links/:id { "enabled": false } # Lead ↔ group map: GET /v1/mirror-links/:id/threads # → [{ "seq": 1, "leadJid": "549...@s.whatsapp.net", "groupJid": "1203...@g.us", ... }] ``` Constraints: one mirror link per session; the human agent must allow being added to groups (WhatsApp privacy setting); deleting a link keeps the WhatsApp groups but stops all relaying. ## Endpoint reference | Method | Path | Auth | |--------|---------------------------|---------------| | POST | /v1/auth/register | Public | | POST | /v1/auth/login | Public | | GET | /v1/sessions | JWT / API key | | POST | /v1/sessions | JWT / API key | | GET | /v1/sessions/:id | JWT / API key | | POST | /v1/sessions/:id/logout | JWT / API key | | GET | /v1/webhooks | JWT / API key | | POST | /v1/webhooks | JWT / API key | | POST | /v1/messages | API key | | GET | /v1/messages | JWT / API key | | GET | /v1/human-agents | JWT / API key | | POST | /v1/human-agents | JWT / API key | | GET | /v1/mirror-links | JWT / API key | | POST | /v1/mirror-links | JWT / API key | ## Errors & limits - `401` — missing or invalid credentials (JWT or API key). - `400` — session not `CONNECTED`, or invalid request body. - `403` — no active subscription, or a plan/trial quota was reached (message cap or connected-number cap). The response message says which. - Webhook deliveries time out after 10 seconds; currently one delivery attempt per event. ## Plans Paid plans with a 7-day free trial (card required). AI agents (bring your included tokens, or your own Anthropic/OpenAI key) are on every plan; MCP tools need Pro+. Starter $8.99/mo: 1 number, 5,000 messages/mo, 2 team members, 2 human agents, 1 flow, 1 webhook, 30-day history. Pro $24.99/mo: 3 numbers, 20,000 messages/mo, 10 team members, 10 human agents, 3 flows, unlimited webhooks, 90-day history. Business $79.99/mo: 10 numbers, 100,000 messages/mo, unlimited team/human agents/flows/webhooks, full history. Annual billing on every plan at 10x monthly (2 months free): $89.90 / $249.90 / $799.90 per year. Message caps count inbound + outbound. During a trial: 300 messages (numbers follow the plan). Full machine-readable breakdown: https://www.whathooks.app/pricing.md ## Product extras (dashboard) - Shared team inbox: multiple team members answer the same number from the web dashboard, with roles (owner/admin/member/operator — operators only see conversations assigned to them), full history, tags, internal notes, quick replies (saved canned responses), assignment, and per-member session access restrictions. - Flows (beta): a visual routing editor per session. Nodes: keyword match, AI intent classification, AI agent reply with handoff, assign to one human / round-robin / a shared team group (any member replies as the brand), tag conversation, assign teammate, save contact, trigger webhook. Handoffs can copy the conversation history into the mirror group. A runs panel records each message's path. An AI assistant drafts a flow from a plain-language description. - Contacts: an org-wide address book (name, phone/LID, company, email, website, Instagram, notes) tracking which numbers each person wrote to. Filled by the "Save contact" flow node, per-session auto-save, or manually; changes fire the contact.* webhooks. - AI agents: auto-responders per session with human handoff. Each agent runs either on included tokens (GPT-5.6 Luna, paid by whathooks: 1M/5M/10M per month on Starter/Pro/Business) or on your own Anthropic/OpenAI key, which is unmetered. MCP tool support on Anthropic agents (Pro+). Knowledge documents (PDF/TXT/MD/CSV, up to 5 docs / 100k characters per agent) ground the agent's answers in your own content. Built-in tools: `handoff_to_human` (optional; pauses the agent for an operator) and `notify_owner` (emails the account owner — reference it in the agent's instructions to use it, e.g. "when someone asks for a quote, use notify_owner with their details"). Human-readable docs: https://www.whathooks.app/docs