# Assign an agent to a WhatsApp number

> POST https://api.nabrah.ai/api/ext/omni_v2/whatsapp/assign_chatbot

Group: WhatsApp. Page: https://www.nabrah.ai/en/docs/api-reference/whatsapp/assign-an-agent-to-a-whatsapp-number

Attach an AI agent to one of the project's WhatsApp numbers so it automatically replies to incoming WhatsApp messages on that number. You bind an existing agent (by `agent_id`) and configure, for this number, whether the bot may escalate to a human, who it hands over to, when idle conversations close, and where conversation events are sent. The response returns the bound agent's full configuration plus this number's `text_chatbot_settings`.

## How auto-reply works

Once assigned, incoming WhatsApp messages on that number are handled by the chosen agent, which generates and sends replies automatically.

- **agent_id**: The UUID of an existing text/basic agent in this project that should answer messages. Fields: `agent_id`.
- **name**: The bot's display name. It is shared by every WhatsApp number this agent answers on. Fields: `name`.
- **Human handoff**: Set `can_refer_to_human` to true to let the bot escalate a conversation, then describe who it reaches in `human_emails` (see below). Fields: `can_refer_to_human`, `human_emails`.

Use GET /api/ext/omni_v2/whatsapp/get_chatbot to inspect the current assignment and DELETE /api/ext/omni_v2/whatsapp/unassign_chatbot to remove it.

## One agent, several numbers

Assigning adds this number to the agent. The agent keeps answering on its other numbers, and their settings stay as they are.

- **Per-number settings**: These apply to the selected number only. Fields: `can_refer_to_human`, `human_emails`, `auto_resolve_hours`, `webhook_url`, `webhook_hmac_secret`.
- **Settings left out**: Leaving out `can_refer_to_human` turns handoff off, and leaving out `human_emails` sends an empty list, which hands over to a random project member who can see this number's conversations. Leaving out `auto_resolve_hours`, `webhook_url` or `webhook_hmac_secret` keeps this number's current value, also when the request replaces the number's agent. On a number with no agent, every setting left out is copied from the agent's first number when the agent already answers on another number, and starts empty otherwise. Fields: `can_refer_to_human`, `human_emails`, `auto_resolve_hours`, `webhook_url`, `webhook_hmac_secret`.
- **Replacing an agent**: A number has one agent at a time. Assigning a different agent to a number replaces the previous one there; ongoing conversations on that number continue with the new agent, which does not see the previous agent's replies. The number keeps its own settings, except those sent in the request and the handoff rule above.
- **inbox_ids**: `text_chatbot_settings.inbox_ids` in the response lists every WhatsApp number the agent now answers on. Fields: `inbox_ids`.

## Choosing who the bot hands over to

`human_emails` accepts three shapes. Working hours are optional on any destination; when they are set, only destinations inside their window are eligible and one of them is picked at random.

- **Anyone available** (human_emails: []): An empty list refers the conversation to a random project member who can see this number's conversations. Working hours are ignored on this path. Fields: `human_emails`.
- **Specific people** (human_emails: ["a@example.com", { "email": "b@example.com", "hours": { ... } }]): A list of addresses. Each item is either a bare string (always available) or an object with an optional `hours` window. Fields: `human_emails`.
- **By department** (human_emails: { "IT": { "desc": "...", "emails": [ ... ] } }): A category map. `desc` is required and is the only text the assistant sees when deciding which department fits, so make it specific. The assistant must pass a category on every call; if none of the descriptions match, nobody is contacted. Fields: `human_emails`.
- **hours** ({ "days": [1,2,3,4,5], "start": "09:00", "end": "17:00", "timezone": "Asia/Riyadh" }): `days` are ISO weekdays, 1 = Monday through 7 = Sunday; an empty list means every day. `start` and `end` are zero-padded 24-hour times, and a window whose start is later than its end wraps past midnight. `timezone` is an IANA zone and defaults to Asia/Riyadh. Fields: `human_emails`.

Addresses that do not belong to a project member who can see this number's conversations still open the conversation, but it is left unassigned.

## Choosing the WhatsApp number

A project can connect more than one WhatsApp number. `inbox_id` selects the number this call works on.

- **One number**: `inbox_id` is optional; the project's only WhatsApp number is used.
- **More than one number**: `inbox_id` is required and the call returns 400 without it. An id that is not one of the project's WhatsApp numbers returns 404. Fields: `inbox_id`.

List the numbers and their ids with GET /api/ext/omni_v2/whatsapp/inboxes.

## Errors

- **409 Conflict**: Another change to the same number's agent, or to the same agent on another number, is still being saved. Wait a moment, then try again; GET /api/ext/omni_v2/whatsapp/get_chatbot shows the result once it is saved.

## Event webhooks

Set `webhook_url` to receive a POST for each chatbot event on this number. Leave the field out to keep the current value, or send null to turn webhooks off.

- **Events**: `message.received`: every incoming customer message on the number, including chats a person has taken over. `message.replied`: a reply sent by the bot. `message.sent`: a reply sent by a person or through the API. `conversation.status_changed`: a handoff to a person, the bot ending the chat, or a manual status change. `conversation.auto_resolved`: the chat was closed after the auto-resolve window. Fields: `webhook_url`.
- **Payload** ({ "event", "occurred_at", "project_id", "agent_id", "conversation_id", "data" }): For message events, `data` has `conversation_id`, `content`, `content_type`, `created_at`, `message_type` (0 = incoming, 1 = outgoing) and `attachments` (each with `file_type` and `data_url`). For status events, `data` has `conversation_id`, `status`, `previous_status` (can be null) and `reason` (`refer_to_human`, `end_chat`, `auto_resolve` or `manual`).
- **Signature** (X-Nabrah-Signature: sha256=<hex digest>): When `webhook_hmac_secret` is set, each request has `X-Nabrah-Timestamp` (Unix seconds) and `X-Nabrah-Signature`: `sha256=` followed by the hex HMAC-SHA256 of `<timestamp>.<raw request body>`, keyed with your secret. Verify it against the raw body, compare in constant time, and reject old timestamps. Every request also has `X-Nabrah-Event` with the event name. Fields: `webhook_hmac_secret`.
- **Delivery**: Each event is sent once with a 5-second timeout. Failed or non-2xx deliveries are not retried, and events have no event ID. One status change can arrive as two events, for example `conversation.auto_resolved` and then `conversation.status_changed`. Keep your handler idempotent, and use GET /api/ext/omni_v2/conversation/conversation/{id}/messages to fill any gaps.

Events are only sent while a chatbot is assigned to the number. GET /api/ext/omni_v2/whatsapp/get_chatbot returns `webhook_url` and `has_webhook_hmac_secret`; the secret itself is never returned.

## Authorization

- `X-API-Key` (string, header, required): your workspace API key.

## Query parameters

- `inbox_id` (string, optional): The `id` of the WhatsApp number to use, from GET /api/ext/omni_v2/whatsapp/inboxes. Use `inboxes[].id` (a UUID), not the numeric `inboxes[].inbox_id`. Optional while the project has one WhatsApp number; required once it has more than one.

## Body (application/json)

### Example: Specific people

A flat list of addresses, always available.

```json
{
  "agent_id": "00000000-0000-4000-8000-000000000000",
  "name": "WhatsApp Support Bot",
  "can_refer_to_human": true,
  "human_emails": [
    "support@example.com"
  ]
}
```

### Example: With working hours

Only reachable Monday to Friday, 09:00-17:00 Riyadh time.

```json
{
  "agent_id": "00000000-0000-4000-8000-000000000000",
  "name": "WhatsApp Support Bot",
  "can_refer_to_human": true,
  "human_emails": [
    "support@example.com",
    {
      "email": "oncall@example.com",
      "hours": {
        "days": [
          1,
          2,
          3,
          4,
          5
        ],
        "start": "09:00",
        "end": "17:00",
        "timezone": "Asia/Riyadh"
      }
    }
  ]
}
```

### Example: By department

The assistant picks a department from the descriptions.

```json
{
  "agent_id": "00000000-0000-4000-8000-000000000000",
  "name": "WhatsApp Support Bot",
  "can_refer_to_human": true,
  "human_emails": {
    "IT": {
      "desc": "Bugs, outages, login and password problems",
      "emails": [
        "it@example.com"
      ]
    },
    "Sales": {
      "desc": "Pricing, quotes and product demos",
      "emails": [
        "sales@example.com",
        {
          "email": "nightshift@example.com",
          "hours": {
            "days": [],
            "start": "22:00",
            "end": "06:00",
            "timezone": "UTC"
          }
        }
      ]
    }
  }
}
```

### Example: Anyone available

An empty list refers to a random project member who can see this number's conversations.

```json
{
  "agent_id": "00000000-0000-4000-8000-000000000000",
  "name": "WhatsApp Support Bot",
  "can_refer_to_human": true,
  "human_emails": []
}
```

### Example: With event webhook

Send chatbot events to your endpoint, signed with a shared secret.

```json
{
  "agent_id": "00000000-0000-4000-8000-000000000000",
  "name": "WhatsApp Support Bot",
  "can_refer_to_human": true,
  "human_emails": [
    "support@example.com"
  ],
  "webhook_url": "https://example.com/nabrah/events",
  "webhook_hmac_secret": "replace-with-a-long-random-secret"
}
```

## Response

### 200 Successful Response

Content type: `application/json`

```json
{
  "agent": {
    "agent_id": "00000000-0000-4000-8000-000000000000",
    "project_id": "00000000-0000-4000-8000-000000000000",
    "created_by_email": "string",
    "created_at": "2026-01-15T12:00:00.000Z",
    "last_update": "2026-01-15T12:00:00.000Z",
    "name": "string",
    "start_first": false,
    "first_sentence": "string",
    "agent_type": "basic",
    "who_are_you": "string",
    "goal": "string",
    "steps": "string",
    "allow_interruptions": false,
    "voice": "dania",
    "support_data": "string",
    "languages": [
      "ar"
    ],
    "stack": "latest",
    "speech_speed": 0.7,
    "variables": [
      {
        "name": "string",
        "type": "string",
        "default_value": "string"
      }
    ],
    "pre_call_callback": {
      "url": "string",
      "parameters": [
        {
          "name": null,
          "location": null,
          "data_type": null,
          "value": null
        }
      ]
    },
    "post_call_callback": {
      "url": "string",
      "parameters": [
        {
          "name": null,
          "location": null,
          "data_type": null,
          "value": null
        }
      ]
    },
    "post_analysis_callback": {
      "url": "string",
      "parameters": [
        {
          "name": null,
          "location": null,
          "data_type": null,
          "value": null
        }
      ]
    },
    "tools": [
      {
        "id": "00000000-0000-4000-8000-000000000000",
        "name": "string"
      }
    ],
    "system_tools": [
      {
        "id": "00000000-0000-4000-8000-000000000000",
        "name": "string",
        "config": {},
        "created_at": "2026-01-15T12:00:00.000Z",
        "updated_at": "2026-01-15T12:00:00.000Z",
        "when_to_use": "string"
      }
    ],
    "analysis_groups": [
      {
        "id": "00000000-0000-4000-8000-000000000000",
        "name": "string"
      }
    ],
    "background_ambient": false,
    "background_ambient_volume": 0,
    "thinking_sound": false,
    "thinking_sound_volume": 0,
    "language_mode": "single"
  },
  "text_chatbot_settings": {
    "can_refer_to_human": false,
    "human_emails": [
      "string"
    ],
    "name": "string",
    "created_at": "2026-01-15T12:00:00.000Z",
    "auto_resolve_hours": 0,
    "webhook_url": "string",
    "has_webhook_hmac_secret": false,
    "inbox_ids": [
      "00000000-0000-4000-8000-000000000000"
    ]
  }
}
```

### 422 Validation Error

Content type: `application/json`

```json
{
  "detail": [
    {
      "loc": [
        "string"
      ],
      "msg": "string",
      "type": "string"
    }
  ]
}
```

## Request examples

### cURL

```bash
curl --request POST \
  --url 'https://api.nabrah.ai/api/ext/omni_v2/whatsapp/assign_chatbot' \
  --header 'X-API-Key: <your-api-key>' \
  --header 'Content-Type: application/json' \
  --data '{
  "agent_id": "00000000-0000-4000-8000-000000000000",
  "name": "WhatsApp Support Bot",
  "can_refer_to_human": true,
  "human_emails": [
    "support@example.com"
  ]
}'
```

### Python

```python
import requests

url = "https://api.nabrah.ai/api/ext/omni_v2/whatsapp/assign_chatbot"
headers = {
    "X-API-Key": "<your-api-key>",
}
payload = {
    "agent_id": "00000000-0000-4000-8000-000000000000",
    "name": "WhatsApp Support Bot",
    "can_refer_to_human": True,
    "human_emails": [
        "support@example.com"
    ]
}

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()
print(response.json())
```

### JavaScript

```javascript
const response = await fetch("https://api.nabrah.ai/api/ext/omni_v2/whatsapp/assign_chatbot", {
  method: "POST",
  headers: {
    "X-API-Key": "<your-api-key>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "agent_id": "00000000-0000-4000-8000-000000000000",
    "name": "WhatsApp Support Bot",
    "can_refer_to_human": true,
    "human_emails": [
      "support@example.com"
    ]
  }),
});

const data = await response.json();
console.log(data);
```

### Go

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

func main() {
	payload := strings.NewReader(`{
  "agent_id": "00000000-0000-4000-8000-000000000000",
  "name": "WhatsApp Support Bot",
  "can_refer_to_human": true,
  "human_emails": [
    "support@example.com"
  ]
}`)

	req, _ := http.NewRequest("POST", "https://api.nabrah.ai/api/ext/omni_v2/whatsapp/assign_chatbot", payload)
	req.Header.Set("X-API-Key", "<your-api-key>")
	req.Header.Set("Content-Type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```
