> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ugatext.com/llms.txt
> Use this file to discover all available pages before exploring further.

# WhatsApp Gateway API

> Connect external systems, CRM databases, ERP platforms, and custom servers to your connected WhatsApp phone.

Connect external systems, CRM databases, ERP platforms, and custom servers to your connected WhatsApp phone. Once a device scans the pairing QR code, a unique API key is generated for that session so you can send messages directly through that phone.

> **Multi-device daemon**
>
> Runs standard WhatsApp Web socket connections in the background, with no browser instances on your servers.

> **Session-isolated API keys**
>
> Every paired device gets a unique session-scoped API key. Any request with that key sends through that phone.

> **No template approvals**
>
> Send freeform receipts, notifications, media, and interactive buttons without Meta review.

**Common uses:** SaaS and CRM, eCommerce, internal tools, mobile apps, marketing and alerts, and transactional messaging.

## Base URLs

| API | Base URL |
| - | - |
| Messaging | `https://ugatext.com/api/whatsapp` |
| Sessions | `https://ugatext.com/api/whatsapp-sessions` |

## Quickstart

When you create a session slot and scan the QR code with WhatsApp on your phone, a dedicated session daemon starts and a unique session-scoped API key is generated for that phone.

**1. Create a session slot**

Create a session in your dashboard or with [`POST /api/whatsapp-sessions`](#create-a-session-slot).

**2. Scan the QR code with your phone**

Open WhatsApp on your phone, tap **Linked Devices**, and scan the QR code.

**3. Use the session API key**

Pass your session API key in the `x-api-key` header to send messages through that phone.

```bash Minimal send message example theme={null}
curl -X POST "https://ugatext.com/api/whatsapp/send-message" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_SESSION_API_KEY" \
  -d '{"to":"256700000000","message":"Hello, your order #502 has been received."}'
```

## Authentication

Every request to the WhatsApp Gateway must be authenticated with either a session API key or account-wide credentials.

### Keys and identifiers

| Identifier | Format | Example | Used for |
| - | - | - | - |
| Session-scoped API key | `uga_wa_` followed by 48 hex characters | `uga_wa_9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a` | Authentication. Scopes the request to its session. |
| Session token | 64-character lowercase hexadecimal | `a3f9c2e18b4d7065f12a9c8e4b3d2f1098a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3` | Authentication. Scopes the request to its session. |
| Account API key | `ug_live_<hex>` (production) or `ug_test_<hex>` (sandbox) | `ug_live_...` | Account-wide authentication. Requires `sessionId` to choose a device. |
| Personal Access Token | `uga_pat_...` | `uga_pat_...` | Account-wide authentication. Requires `sessionId` to choose a device. |
| Session ID | `wa_<timestamp>_<hex>` | `wa_1711890000000_a1b2c3d4` | URL path parameters (`/api/whatsapp-sessions/{whatsappSession}/...`) and the `sessionId` body field. Not a credential. |
| Message ID | `wamsg_<timestamp>_<hex>` | `wamsg_1711890400000_d8a1b2c3` | Returned by every send endpoint as `messageId`. |

**Session-scoped key (recommended)**

When you pair your WhatsApp device, a session-scoped key is generated for that session: either a `uga_wa_...` key or a 64-character hexadecimal session token. Send it in the `x-api-key` header or as `Authorization: Bearer <key>`. The request is automatically scoped to that session, so you don't need a `sessionId` in the body.

```http theme={null}
x-api-key: uga_wa_9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a
```

```http theme={null}
Authorization: Bearer a3f9c2e18b4d7065f12a9c8e4b3d2f1098a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3
```

**Account-wide credentials**

If your backend manages several devices centrally, authenticate with your account API key (`ug_live_...` or `ug_test_...`) or a workspace-wide Personal Access Token (`uga_pat_...`). Choose the device by passing its session ID in the `sessionId` body field.

```http theme={null}
Authorization: Bearer ug_live_YOUR_ACCOUNT_API_KEY
```

```http theme={null}
Authorization: Bearer uga_pat_YOUR_PERSONAL_ACCESS_TOKEN
```

Personal Access Tokens also work in the `x-api-key` header (`x-api-key: uga_pat_...`).

> [!NOTE]
>
> Standard [Client-ID and Client-Secret headers](https://docs.ugatext.com/authentication.md) also work for account-level WhatsApp requests, either as separate `Client-ID` and `Client-Secret` headers or as `Authorization: Bearer <Client-ID>:<Client-Secret>`.

```json theme={null}
{
  "to": "256772123456",
  "message": "Hello from the central backend",
  "sessionId": "wa_1711890000000_a1b2c3d4"
}
```

| Header | Type | Required | Description |
| - | - | - | - |
| `x-api-key` | string | Yes | The session-scoped key (`uga_wa_...` or 64-character hex token) for your paired WhatsApp phone. |
| `Authorization` | string | No | Bearer alternative: `Bearer <session_key>`, `Bearer ug_live_...`, or `Bearer uga_pat_...`. |
| `Content-Type` | string | Yes | Must be `application/json` for all POST requests. |
| `Accept` | string | No | Must be `application/json`. |

### Session API key credentials

The **Session API Key Credentials** panel in the dashboard lists every WhatsApp session slot on your account. For each slot it shows:

* The linked phone number and push name, or `Pending Scan` before pairing completes.
* The session ID used in request paths such as `/api/whatsapp-sessions/{sessionId}/status`.
* The session-scoped API key, with a copy button.
* The connection status, and a control to choose which slot the documentation examples use.

> [!NOTE]
>
> A session API key only appears after a device is paired. If you have no session yet, open the WhatsApp Dashboard, start a session slot, and scan the QR code with your phone.

## Sessions and devices

In the paths below, `:whatsappSession` is the session ID, for example `wa_1711890000000_a1b2c3d4`.

### List sessions

`GET /api/whatsapp-sessions`

Returns all session slots, connection states, device battery levels, phone numbers, and associated API keys on your account.

```bash Request theme={null}
curl -X GET "https://ugatext.com/api/whatsapp-sessions" \
  -H "x-api-key: YOUR_SESSION_API_KEY" \
  -H "Accept: application/json"
```

```json Response (200) theme={null}
{
  "success": true,
  "count": 1,
  "sessions": [
    {
      "status": "connected",
      "deviceInfo": {
        "platform": "WhatsApp for Android",
        "pushName": "Sales Support",
        "batteryLevel": 94
      },
      "connectedAt": "2026-09-08T10:14:22.000Z"
    }
  ]
}
```

### Create a session slot

`POST /api/whatsapp-sessions`

Creates a new session. Returns the new session ID and a freshly generated API key for that slot.

| Parameter | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | A label for the device slot, for example `Sales Desk` or `Kampala Logistics`. |
| `phone` | string | No | Target phone number with country code and no plus sign, for example `256700123456`. |
| `accountProtection` | boolean | No | Enables automatic throttling and warm-up pacing to protect your WhatsApp account. |
| `enableWebhooks` | boolean | No | Enables real-time event forwarding to your webhook endpoint. |

```bash Request theme={null}
curl -X POST "https://ugatext.com/api/whatsapp-sessions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ug_live_YOUR_ACCOUNT_API_KEY" \
  -d '{"name":"Kampala Branch Support","phone":"256700123456","accountProtection":true,"enableWebhooks":true}'
```

```json Response (200) theme={null}
{
  "success": true,
  "data": {
    "id": "wa_1711890000000_a1b2c3d4",
    "userId": "usr_987654321",
    "name": "Kampala Support Line",
    "phone_number": "+256772123456",
    "status": "need_scan",
    "account_protection": true,
    "message_logging": true,
    "read_incoming": false,
    "webhook_url": "https://api.yourdomain.ug/webhooks/whatsapp",
    "webhook_enabled": true,
    "webhook_events": [
      "whatsapp.message.received",
      "whatsapp.message.sent",
      "whatsapp.message.delivered",
      "whatsapp.message.read",
      "whatsapp.session.status",
      "whatsapp.poll.vote"
    ],
    "api_key": "a3f9c2e18b4d7065f12a9c8e4b3d2f1098a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3",
    "created_at": "2025-05-14T10:00:00.000Z"
  }
}
```

`data.id` is the session ID and `data.api_key` is the session API key.

### Get the QR code

`GET /api/whatsapp-sessions/:whatsappSession/qrcode`

Returns the current pairing QR code as a base64 data URL or raw string matrix. Display it in your application for the phone to scan.

```bash Request theme={null}
curl -X GET "https://ugatext.com/api/whatsapp-sessions/wa_1711890000000_a1b2c3d4/qrcode" \
  -H "x-api-key: YOUR_SESSION_API_KEY" \
  -H "Accept: application/json"
```

```json Response (200) theme={null}
{
  "success": true,
  "status": "qr_ready",
  "qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA...",
  "expiresInSeconds": 60
}
```

### Pair with a phone number

`POST /api/whatsapp-sessions/:whatsappSession/pair`

Links a device with an 8-digit pairing code instead of a QR code.

| Parameter | Type | Required | Description |
| - | - | - | - |
| `phone` | string | Yes | The phone number to pair, including country code, for example `256700123456`. |

```bash Request theme={null}
curl -X POST "https://ugatext.com/api/whatsapp-sessions/wa_1711890000000_a1b2c3d4/pair" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_SESSION_API_KEY" \
  -d '{"phone":"256700123456"}'
```

### Check device health

`GET /api/whatsapp-sessions/:whatsappSession/status`

Checks whether the device is linked, its battery level, phone platform, and whether the connection is ready to send messages.

```bash Request theme={null}
curl -X GET "https://ugatext.com/api/whatsapp-sessions/wa_1711890000000_a1b2c3d4/status" \
  -H "x-api-key: YOUR_SESSION_API_KEY" \
  -H "Accept: application/json"
```

```json Response (200) theme={null}
{
  "success": true,
  "status": "connected",
  "qrCode": null,
  "pairingCode": null,
  "connectedPhone": "256772123456",
  "lastError": null
}
```

While the session waits for authentication, `status` is `qr_ready` and `qrCode` contains a `data:image/png;base64,...` data URL.

### Restart, disconnect, and rotate keys

| Endpoint | Description |
| - | - |
| `POST /api/whatsapp-sessions/:whatsappSession/restart` | Soft restarts the WebSocket connection to resolve transient network timeouts without losing linked credentials. |
| `POST /api/whatsapp-sessions/:whatsappSession/regenerate-key` | Immediately invalidates the current session API key and issues a new one. |
| `POST /api/whatsapp-sessions/:whatsappSession/disconnect` | Logs the WhatsApp session out of the phone and clears local authentication state. |

## Send messages

All messaging endpoints accept an optional `sessionId` (`wa_...`). You only need it when you authenticate with account-wide credentials instead of a session API key.

> [!NOTE]
>
> **Phone number format:** every WhatsApp endpoint accepts international numbers with or without the leading `+` (`256772123456` or `+256772123456`). Spaces, hyphens, and `+` are stripped automatically, and responses always return the digits-only number, for example `"to": "256772123456"`. Examples on this page use digits-only.

### Text message

`POST /api/whatsapp/send-message`

Sends a formatted WhatsApp message to any valid phone number. Supports formatting, line breaks, and dynamic merge tags.

| Parameter | Type | Required | Description |
| - | - | - | - |
| `to` | string | Yes | Recipient number in international format, with or without `+`, or a group JID. Example: `256772123456`. |
| `message` | string | Yes | Text to send. Supports WhatsApp formatting: `*bold*`, `_italic_`, `~strike~`. |
| `sessionId` | string | No | Session ID (`wa_...`). Optional when you authenticate with a session API key. |

```bash Request theme={null}
curl -X POST "https://ugatext.com/api/whatsapp/send-message" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_SESSION_API_KEY" \
  -d '{"to":"256700123456","message":"Your payment of UGX 50,000 has been confirmed. Receipt #UG-9021."}'
```

```json Response (200) theme={null}
{
  "success": true,
  "message": "WhatsApp message dispatched successfully.",
  "messageId": "wamsg_1711890400000_d8a1b2c3",
  "status": "sent",
  "timestamp": "2026-09-08T10:20:00.000Z"
}
```

### Media and files

`POST /api/whatsapp/send-media`

Sends images (PNG, JPEG, WebP), documents (PDF, DOCX, XLSX), voice notes, or video from a publicly accessible URL.

| Parameter | Type | Required | Description |
| - | - | - | - |
| `to` | string | Yes | Recipient number in international format, with or without `+`, or a group JID. Example: `256772123456`. |
| `mediaUrl` | string | Yes | Public HTTPS URL of the file. Example: `https://example.com/invoice.pdf`. |
| `mediaType` | string | Yes | One of `image`, `document`, `video`, `audio`. |
| `caption` | string | No | Caption sent with the media. |
| `fileName` | string | No | File name shown to the recipient for documents. Example: `Invoice_8801.pdf`. |

```bash Request theme={null}
curl -X POST "https://ugatext.com/api/whatsapp/send-media" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_SESSION_API_KEY" \
  -d '{"to":"256700123456","mediaUrl":"https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf","mediaType":"document","fileName":"Invoice_8801.pdf","caption":"Please find attached your statement for August."}'
```

### Location pin

`POST /api/whatsapp/send-location`

Sends a clickable Google Maps pin with latitude, longitude, venue name, and address.

| Parameter | Type | Required | Description |
| - | - | - | - |
| `to` | string | Yes | Recipient number in international format, with or without `+` (for example `256772123456`), or a group JID (`12036304...@g.us`). |
| `latitude` | number | Yes | WGS84 latitude between -90 and 90, for example `0.3476`. |
| `longitude` | number | Yes | WGS84 longitude between -180 and 180, for example `32.5825`. |
| `name` | string | No | Title shown on the pin. Defaults to `Location Pin`. Max 100 characters. |
| `address` | string | No | Street address or subtitle shown below the name. Max 256 characters. |
| `sessionId` | string | No | Session ID (`wa_...`). Optional when you authenticate with a session API key. |

```bash Request theme={null}
curl -X POST "https://ugatext.com/api/whatsapp/send-location" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ug_live_YOUR_ACCOUNT_API_KEY" \
  -d '{
    "to": "256772123456",
    "latitude": 0.3476,
    "longitude": 32.5825,
    "name": "UgaText Headquarters",
    "address": "Plot 24 Kampala Road, Kampala, Uganda",
    "sessionId": "wa_1711890000000_a1b2c3d4"
  }'
```

### Contact card (vCard)

`POST /api/whatsapp/send-contact`

Shares contact details that the recipient can save to their phonebook in one tap.

| Parameter | Type | Required | Description |
| - | - | - | - |
| `to` | string | Yes | Recipient number in international format, with or without `+`, or a group JID. Example: `256772123456`. |
| `contactName` | string | Yes | Full display name (`FN`) in the vCard 3.0 payload. Max 120 characters. |
| `contactPhone` | string | Yes | Phone number of the shared contact in E.164 format, for example `+256701234567`. |
| `organization` | string | No | Company or organization name (`ORG` in the vCard). Max 120 characters. |
| `sessionId` | string | No | Session ID (`wa_...`). Optional when you authenticate with a session API key. |

```bash Request theme={null}
curl -X POST "https://ugatext.com/api/whatsapp/send-contact" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_SESSION_API_KEY" \
  -d '{"to":"256700123456","contactName":"Sarah Namukasa","contactPhone":"+256701234567","organization":"Customer Experience Team"}'
```

### Quick reply buttons

`POST /api/whatsapp/send-buttons`

Sends structured choices for confirmations, order approvals, or survey branches.

| Parameter | Type | Required | Description |
| - | - | - | - |
| `to` | string | Yes | Recipient number in international format, with or without `+`, or a group JID. Example: `256772123456`. |
| `body` | string | Yes | Main message text. Max 1,024 characters. |
| `buttons` | object\[] | Yes | 1 to 3 buttons. Each needs an `id` (string, max 256 characters) and a `title` or `text` (string, max 20 characters). |
| `title` | string | No | Bold header shown above the body. Max 60 characters. |
| `footer` | string | No | Footer text shown below the body. Max 60 characters. |
| `sessionId` | string | No | Session ID (`wa_...`). Optional when you authenticate with a session API key. |

```bash Request theme={null}
curl -X POST "https://ugatext.com/api/whatsapp/send-buttons" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_SESSION_API_KEY" \
  -d '{"to":"256700123456","title":"Order Status Update","body":"Your package is ready for delivery. Please confirm your availability:","footer":"Reply with button or type answer","buttons":[{"id":"btn_confirm","title":"Confirm Delivery"},{"id":"btn_reschedule","title":"Reschedule Date"}]}'
```

### Poll

`POST /api/whatsapp/send-poll`

Sends a native WhatsApp poll to a single recipient or a group. Votes arrive through the [`whatsapp.poll.vote`](#events) webhook.

| Parameter | Type | Required | Description |
| - | - | - | - |
| `to` | string | Yes | Recipient number in international format, with or without `+`, or a group JID. Example: `256772123456`. |
| `question` | string | Yes | The poll question. Max 255 characters. |
| `options` | string\[] | Yes | 2 to 12 choices. Each choice max 100 characters. |
| `multipleAnswers` | boolean | No | `true` lets recipients pick several options. Defaults to `false` (single selection, `selectableCount: 1`). |
| `sessionId` | string | No | Session ID (`wa_...`). Optional when you authenticate with a session API key. |

```bash Request theme={null}
curl -X POST "https://ugatext.com/api/whatsapp/send-poll" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_SESSION_API_KEY" \
  -d '{"to":"256700123456","question":"How would you rate your support experience today?","options":["Excellent (5 stars)","Good (4 stars)","Needs Improvement"],"multipleAnswers":false}'
```

### Template message

`POST /api/whatsapp/send-template`

Sends a saved template from your UgaText Templates library, chosen by `templateName` or `templateId`, and fills its `{{variable}}` placeholders. If no saved template matches `templateName`, the API formats a structured notification on the fly, using `templateName` as its title.

If the recipient's number exists in UgaText Contacts, their contact fields (`firstName`, `lastName`, `name`, `phone`, `email`, and custom fields) are merged into the placeholders automatically.

| Parameter | Type | Required | Description |
| - | - | - | - |
| `to` | string | Yes | Recipient number in international format, with or without `+`. Example: `256772123456`. |
| `templateName` | string | Conditional | Name of a saved template in your dashboard, or the title of an ad-hoc structured message. Required if `templateId` is not provided. |
| `templateId` | string | Conditional | ID of a saved template in your dashboard. Required if `templateName` is not provided. |
| `parameters` | object | No | Key-value map of strings that replace `{{key}}` or `{key}` placeholders in the template body. |
| `sessionId` | string | No | Session ID. Defaults to your first connected session. Optional when you authenticate with a session API key. |

```bash Request theme={null}
curl -X POST "https://ugatext.com/api/whatsapp/send-template" \
  -H "Authorization: Bearer ug_live_YOUR_ACCOUNT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "256772123456",
    "templateName": "order_confirmation",
    "parameters": {
      "firstName": "Sarah",
      "orderId": "ORD-9042",
      "amount": "UGX 85,000"
    },
    "sessionId": "wa_1711890000000_a1b2c3d4"
  }'
```

```json Response (200) theme={null}
{
  "success": true,
  "message": "Template dispatched successfully.",
  "templateName": "order_confirmation",
  "content": "Hello Sarah, your order ORD-9042 for UGX 85,000 has been confirmed.",
  "messageId": "wamsg_1711890400000_d8a1b2c3",
  "sessionId": "wa_1711890000000_a1b2c3d4",
  "status": "sent",
  "to": "256772123456",
  "costUgx": 45,
  "createdAt": "2026-10-28T16:48:00.000Z"
}
```

### Check WhatsApp registration

`GET /api/whatsapp/check-number`

Checks whether a phone number has an active WhatsApp account before you send, to prevent failed deliveries.

```bash Request theme={null}
curl -X GET "https://ugatext.com/api/whatsapp/check-number?phone=256700123456" \
  -H "x-api-key: YOUR_SESSION_API_KEY" \
  -H "Accept: application/json"
```

```json Response (200) theme={null}
{
  "success": true,
  "registered": true,
  "formattedJid": "256700123456@s.whatsapp.net",
  "phone": "256700123456"
}
```

## Contacts and groups

### Contacts

`GET /api/whatsapp/contacts`

Returns contacts synced from the connected phone, including names and JIDs.

```bash Request theme={null}
curl -X GET "https://ugatext.com/api/whatsapp/contacts" \
  -H "x-api-key: YOUR_SESSION_API_KEY" \
  -H "Accept: application/json"
```

```json Response (200) theme={null}
{
  "success": true,
  "data": [
    {
      "id": "256772123456@s.whatsapp.net",
      "name": "Sarah Namukasa",
      "notify": "Sarah",
      "phone": "256772123456"
    },
    {
      "id": "256701987654@s.whatsapp.net",
      "name": "David Ochieng",
      "notify": "David O.",
      "phone": "256701987654"
    }
  ]
}
```

### Groups

`GET /api/whatsapp/groups`

Returns the groups the connected phone belongs to, with group JIDs, subjects, and participants.

```bash Request theme={null}
curl -X GET "https://ugatext.com/api/whatsapp/groups" \
  -H "x-api-key: YOUR_SESSION_API_KEY" \
  -H "Accept: application/json"
```

```json Response (200) theme={null}
{
  "success": true,
  "data": [
    {
      "id": "120363041928374650@g.us",
      "subject": "UgaText Engineering Alerts",
      "creation": 1712048400,
      "owner": "256772123456@s.whatsapp.net",
      "size": 18,
      "participants": [
        {
          "id": "256772123456@s.whatsapp.net",
          "admin": "superadmin"
        },
        {
          "id": "256701987654@s.whatsapp.net",
          "admin": null
        }
      ]
    }
  ]
}
```

Use a group `id` (ending in `@g.us`) as the `to` value to send to that group.

## Webhooks

Receive real-time HTTP POST callbacks when inbound messages arrive, outbound messages are sent, or a session's connection state changes. WhatsApp events are sent through the same webhook engine as [SMS webhooks](https://docs.ugatext.com/webhooks.md).

| Setting | Value |
| - | - |
| Method | HTTP POST with `Content-Type: application/json` |
| Timeout | Your endpoint must return a 2xx status within 5 seconds. |
| Retries | Failed deliveries are retried 3 times with exponential backoff (15s, 60s, 300s). |

### Events

Every WhatsApp webhook uses the same top-level envelope as SMS and Wallet webhooks: `id`, `event`, `created_at`, `mode`, and `data`.

| Event | Fired when |
| - | - |
| `whatsapp.message.received` | A user sends an inbound text or media message to your connected WhatsApp device. |
| `whatsapp.message.sent` | An outbound message is sent from your WhatsApp session. |
| `whatsapp.message.delivered` | A sent message is delivered to the recipient's device. |
| `whatsapp.message.read` | The recipient opens and reads your message. |
| `whatsapp.session.status` | A session changes connection state: `connected`, `qr_ready`, `reconnecting`, or `disconnected`. |
| `whatsapp.poll.vote` | A recipient votes on a poll sent with [`POST /api/whatsapp/send-poll`](#poll). |

```json Inbound message payload theme={null}
{
  "id": "evt_1760425810_a1b2c3",
  "event": "whatsapp.message.received",
  "created_at": "2026-10-28T16:45:12.000Z",
  "mode": "live",
  "data": {
    "sessionId": "wa_1711890000000_a1b2c3d4",
    "from": "256772123456",
    "message": "Hello, I would like to check my order status.",
    "mediaUrl": null,
    "timestamp": "2026-10-28T16:45:12.000Z"
  }
}
```

```json Poll vote payload theme={null}
{
  "id": "evt_1760425890_c4d5e6",
  "event": "whatsapp.poll.vote",
  "created_at": "2026-10-28T16:50:00.000Z",
  "mode": "live",
  "data": {
    "sessionId": "wa_1711890000000_a1b2c3d4",
    "pollMessageId": "wamsg_1711890300000_f9e8d7c6",
    "voter": "256772123456",
    "selectedOptions": ["Mobile Money (MTN / Airtel)"],
    "timestamp": "2026-10-28T16:50:00.000Z"
  }
}
```

### Verify signatures

WhatsApp webhooks use the same signature format as SMS webhooks:

```http theme={null}
X-UgaText-Signature: t=<unix_timestamp>,v1=<hex_hmac_sha256>
```

**Signing secret:** the session's custom webhook secret (`webhookSettings.secret`) if one is set on that WhatsApp session. Otherwise, your workspace's primary webhook signing secret (`whsec_...`).

**1. Parse the header**

Split `X-UgaText-Signature` on `,` to get the timestamp `t` and the signature `v1`.

**2. Build the signed payload**

Join the timestamp, a literal period, and the raw, unparsed request body: `<timestamp>.<raw_json_body>`.

**3. Compute the digest**

Compute an HMAC SHA-256 of that string with the signing secret, encoded as lowercase hex.

**4. Compare**

Compare your digest with `v1` using a constant-time function. Reject requests where `t` is more than 300 seconds old.

```javascript Node.js theme={null}
import crypto from 'crypto';

export function verifyWebhookSignature(rawBody, sigHeader, secret) {
  const parts = Object.fromEntries(
    (sigHeader || '').split(',').map((part) => part.split('='))
  );
  const timestamp = parts.t;
  const received = parts.v1 || '';

  if (!timestamp || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return false;
  }

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  if (expected.length !== received.length) {
    return false;
  }

  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}
```

> [!WARNING]
>
> Use the raw request body. Parsing and re-serializing the JSON changes the bytes and breaks verification.

> [!NOTE]
>
> The diagnostic webhook tester (`POST /api/test-webhook`) is the only exception. When a custom secret is provided, it sends a plain hex HMAC SHA-256 digest of the raw body, without the `t=...,v1=` prefix.

See [Webhooks](https://docs.ugatext.com/webhooks.md#verify-signatures) for a full Express handler and a Python example.

## Status codes and quotas

All endpoints return standard HTTP status codes. Rate limits apply per plan to protect device health.

| Status | Meaning |
| - | - |
| `200 OK` | The message or operation succeeded. |
| `400 Bad Request` | Missing required parameters, such as an invalid phone number or empty message body. |
| `401 Unauthorized` | Invalid or expired session API key or token. |
| `404 Not Found` | The session ID does not exist or was deleted. |
| `429 Too Many Requests` | Plan rate limit or daily message quota reached. |
| `503 Service Unavailable` | The WhatsApp phone is disconnected or offline. |

## Testing tools

> **[OpenAPI 3.0 specification](https://ugatext.com/api/whatsapp/openapi.json)**
>
> The complete machine-readable WhatsApp API spec. Import it into Swagger UI, Insomnia, or code generators.

> **[Postman Collection v2.1](https://ugatext.com/api/whatsapp/postman.json)**
>
> A preconfigured collection with environment variable placeholders for one-click testing.

> **Live API console**
>
> The live API console on the ugatext.com documentation page calls real endpoints with your active session key from the browser. Requests go to the backend, so a paired device sends the message.
>
> Available endpoints: `POST /api/whatsapp/send-message`, `POST /api/whatsapp/send-media`, `GET /api/whatsapp/check-number`, and `GET /api/whatsapp-sessions`.
>
> Enter a recipient phone (for example `256700000000`), plus message content for text or a media URL and type (`image`, `document`, `video`, `audio`) for media. The console shows the HTTP status, round-trip time in milliseconds, and the JSON response.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.