What Hookswhathooks
API reference

whathooks documentation

Connect a WhatsApp number, receive inbound messages on your webhook, and send replies through our REST API.

🤖 Building with an AI coding agent? Give it the whole API in one markdown file: whathooks.app/llms.txt

Introduction

whathooks lets your clients connect their own WhatsApp number by scanning a QR code. Once linked, the integration works in two directions:

  • Inbound → webhook. Every message your number receives is POSTed as JSON to a webhook URL you configure.
  • Outbound → API. You send replies by calling our REST API at /v1.

Your number is connected through a WhatsApp Web link, the same mechanism as WhatsApp's “linked devices”. Reconnection after a dropped link is handled automatically, and each number supports one linked device.

Because this uses an unofficial WhatsApp Web connection, treat it like a linked device: keep the phone reachable and avoid behavior that could get the number flagged by WhatsApp.

Quickstart

Get from zero to a sent message in four steps.

  1. 1. Create an account & organization

    Sign up and create an organization. All sessions, webhooks, and API keys live under your organization.

  2. 2. Create a WhatsApp session & scan the QR

    Create a session in the dashboard, then open WhatsApp on your phone → Linked DevicesLink a Device and scan the QR.

  3. 3. Add a webhook URL

    Register an HTTPS endpoint to receive message.received and session events.

  4. 4. Create an API key

    Generate an API key to send messages programmatically via POST /v1/messages.

Connecting a number

A session moves through a series of statuses as it links and stays online. You can poll a session or subscribe to session.status webhooks to track it.

  • PENDINGSession created, not yet initialized.
  • QRA QR code is ready to be scanned with WhatsApp.
  • CONNECTINGPairing accepted, establishing the connection.
  • CONNECTEDOnline and ready to send and receive messages.
  • DISCONNECTEDConnection dropped; reconnection is attempted automatically.
  • LOGGED_OUTThe device was unlinked; a new QR scan is required.

Embed the QR in your product

Sessions are fully manageable with an API key, so your product can onboard users without ever showing them the whathooks dashboard: create a session, then render the pairing QR in your own UI. GET /v1/sessions/:id returns qrDataUrl. A ready-to-embed PNG data URI (<img src={qrDataUrl} />). QR codes rotate, so poll the session while it's in the QR status or subscribe to session.qr webhooks for fresh codes, and watch for CONNECTED to dismiss the QR.

# 1. Create a session (starts pairing, a QR is generated)
curl -X POST https://api.whathooks.com/v1/sessions \
  -H "X-API-Key: wh_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "label": "customer-42" }'
# → { "id": "sess_8f2k19a7", "status": "PENDING", ... }

# 2. Poll until status is "QR", then embed the code in your UI
curl https://api.whathooks.com/v1/sessions/sess_8f2k19a7 \
  -H "X-API-Key: wh_live_..."
# → { "status": "QR", "qrDataUrl": "data:image/png;base64,...", ... }

# 3. Your user scans it with WhatsApp → status becomes "CONNECTED"

Webhooks

When something happens on a session, we POST a JSON body to your configured webhook URL.

Events

EventDescription
message.receivedAn inbound WhatsApp message arrived on a connected session.
session.statusA session changed status (e.g. CONNECTED, DISCONNECTED, LOGGED_OUT).
session.qrA new QR code was generated and is waiting to be scanned.
contact.createdA new contact was saved. Via the session's auto-save setting, a flow's "Save contact" node, or manual creation.
contact.updatedAn existing contact changed: fields edited, name backfilled from WhatsApp, or a new session linked.

Delivery headers

Every delivery includes these request headers:

HeaderDescription
X-Whathooks-EventThe event type, e.g. message.received.
X-Whathooks-SignatureHMAC-SHA256 of the raw body, formatted as sha256=<hex>.
X-Whathooks-DeliveryA unique ID for this delivery attempt.

Payload envelope

Every delivery body shares the same envelope: { event, sessionId, data, timestamp }. Below is a full message.received delivery:

{
  "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"
}

type is uppercase (TEXT, IMAGE, AUDIO, VIDEO, DOCUMENT, STICKER, LOCATION, CONTACT, UNKNOWN). data.timestamp is unix seconds (the WhatsApp message time); the envelope timestamp is the ISO delivery time.

For media messages, media carries a signed download link (expires after 1 hour. Download promptly, or re-fetch the message via GET /v1/messages for a fresh link). text holds the caption, if any:

"type": "IMAGE",
"text": "the caption, if any",
"media": {
  "url": "https://api.whathooks.app/v1/media/raw?key=...&exp=...&sig=...",
  "mimeType": "image/jpeg",
  "fileName": "photo.jpg"
}

Customize the payload

Optionally give a webhook mapping rules to reshape data to match your system: rename fields, format dates, and inject fixed values. When rules are set, data contains only the fields you map. The envelope stays the same. Each rule has a target (output name) and either a source (dot path like data.from, also reaching event, sessionId, timestamp) or a fixed value. Add dateFormat to a source rule to format it as a date: iso, unix, unix_ms, or a UTC pattern using yyyy MM dd HH mm ss. Configure it on the webhook in the dashboard, or pass payloadMapping when creating one via API.

// Mapping rules on the webhook:
[
  { "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" }
]

// What your endpoint receives:
{
  "event": "message.received",
  "sessionId": "sess_8f2k19a7",
  "data": {
    "phone": "15551234567",
    "message": "Hi! Is my order shipped yet?",
    "receivedAt": "2026-07-14 19:30",
    "origin": "whathooks"
  },
  "timestamp": "2026-07-14T22:30:00.000Z"
}

Signature verification

Verify each delivery by computing an HMAC-SHA256 of the raw request body using your webhook secret, then comparing it to the X-Whathooks-Signature header. Always compare with a constant-time function.

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");

  // Header arrives as "sha256=<hex>" — strip the prefix first
  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);
}

Sending messages

Send a message with POST /v1/messages, authenticated with an API key via the X-API-Key: <token> header. An Authorization: Bearer <token> header also works.

The to field accepts a bare phone number (digits only, with country code, no +) or a full WhatsApp JID. The target session must be CONNECTED or the request returns 400.

curl -X POST https://api.whathooks.com/v1/messages \
  -H "X-API-Key: wh_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "sessionId": "sess_8f2a...",
    "to": "15551234567",
    "text": "Hello"
  }'

A successful request responds with the queued message:

{
  "id": "msg_5d72...",
  "waMessageId": "3EB0F1E2D3C4B5A6",
  "sessionId": "sess_8f2a...",
  "to": "15551234567",
  "status": "sent"
}

API reference

Base URL in production: https://api.whathooks.com/v1. Dashboard requests use a JWT (from /v1/auth/login), while programmatic requests use an API key.

MethodPathAuth
POST/v1/auth/registerPublic
POST/v1/auth/loginPublic
GET/v1/sessionsJWT / API key
POST/v1/sessionsJWT / API key
GET/v1/sessions/:idJWT / API key
POST/v1/sessions/:id/logoutJWT / API key
GET/v1/webhooksJWT / API key
POST/v1/webhooksJWT / API key
POST/v1/messagesAPI key
GET/v1/messagesJWT / API key

Errors & limits

  • 401Missing or invalid credentials (JWT or API key).
  • 400The session is not CONNECTED, or the request body is invalid.
  • WebhooksDelivery requests time out after 10 seconds. For now we make a single delivery attempt per event. Automatic retries are coming.