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.
Quickstart
Get from zero to a sent message in four steps.
1. Create an account & organization
Sign up and create an organization. All sessions, webhooks, and API keys live under your organization.
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. Add a webhook URL
Register an HTTPS endpoint to receive
message.receivedand session events.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
| Event | Description |
|---|---|
message.received | An inbound WhatsApp message arrived on a connected session. |
session.status | A session changed status (e.g. CONNECTED, DISCONNECTED, LOGGED_OUT). |
session.qr | A new QR code was generated and is waiting to be scanned. |
Delivery headers
Every delivery includes these request headers:
| Header | Description |
|---|---|
X-Whathooks-Event | The event type, e.g. message.received. |
X-Whathooks-Signature | HMAC-SHA256 of the raw body, formatted as sha256=<hex>. |
X-Whathooks-Delivery | A 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.
| 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 |
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.