whathooks
API reference

whathooks documentation

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

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.

Connections are powered by an unofficial WhatsApp Web link via the Baileys library. 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 Devices Link 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.

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.

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...",
    "sessionId": "sess_8f2a...",
    "from": "15551234567",
    "pushName": "Jane Doe",
    "type": "text",
    "text": "Hi there!",
    "waMessageId": "3EB0A1B2C3D4E5F6",
    "timestamp": "2026-06-29T14:21:07.000Z"
  },
  "timestamp": "2026-06-29T14:21:07.412Z"
}

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.