> For the complete documentation index, see [llms.txt](https://docs.maiagent.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.maiagent.ai/tech/en/serve/customer-service-system-integration.md).

# Third-Party Customer Service System Integration (Including Human Handoff)

Connect a customer service system or chat platform to MaiAgent as an AI engine via API: end-to-end specifications for message exchange, handoff to your human agents when AI determines it is needed, an

This page is for integrators who already have their own customer service system or chat frontend (with channels such as their own website, LINE, or Messenger connected on their side) and want to use MaiAgent as an **AI engine**: your system sends visitor messages to MaiAgent and receives AI replies; when AI determines that human assistance is needed, **your human agents** take over, then return the conversation to AI when service ends.

The integration uses only MaiAgent's existing public APIs and two sets of Webhooks. No dedicated interface needs to be developed for an individual customer service system.

## Two Integration Scenarios <a href="#scenarios" id="scenarios"></a>

| Scenario                                              | Where visitors chat                                                                            | Where human agents work | How to integrate                                                                              |
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------- |
| **Scenario A**: MaiAgent as an AI engine              | Your system (with your website, LINE, and other channels connected on your side)               | Your system             | Complete [Setup](#setup), then follow [Step 1](#step-identity) through [Step 6](#step-return) |
| **Scenario B**: Only human agents work in your system | MaiAgent's conversation platform (Web Chat, LINE Official Account, etc. connected to MaiAgent) | Your system             | Complete [Setup](#setup), then follow [Scenario B](#scenario-b)                               |

If your human agents use Genesys Cloud, for Scenario B you can use the built-in [Genesys Cloud integration](/tech/en/serve/customer-service-system-integration/genesys-cloud-integration.md) instead of building the integration yourself.

Both scenarios share the same human handoff events, [event list](#events), and [state queries](#query-state). The integration overview and Steps 1–6 below describe Scenario A.

## Integration Overview <a href="#overview" id="overview"></a>

```mermaid
sequenceDiagram
    participant V as Visitor
    participant S as Your customer service system
    participant M as MaiAgent
    participant H as Your human agent

    V->>S: Send a message (website / LINE / …)
    S->>M: ① Sync identity (sourceId → contactId)
    S->>M: ② Create a conversation (first time)
    S->>M: ③ Send a message POST /messages/
    M-->>S: 201 (AI reply is asynchronous)
    M-)S: ④ Outgoing Webhook: AI reply
    S->>V: Display the AI reply

    Note over M: AI determines human assistance is needed
    M-)S: ⑤ handoff.requested
    S->>H: Create a ticket and assign an agent
    H->>V: Human agent continues service (in your system)
    S->>M: ⑥ PATCH /conversations/{id}/return-to-ai/
    M-)S: handoff.returned_to_ai
    Note over M: AI resumes replying to subsequent messages
```

### Responsibilities <a href="#responsibilities" id="responsibilities"></a>

| Party           | Responsibilities                                                                                                                                                                                                                 |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Your system** | Channel integration, visitor interface, tickets and agent seats, storing and displaying conversation history; sending visitor messages to MaiAgent, receiving Webhooks, and calling the return-to-AI API when human service ends |
| **MaiAgent**    | AI replies, maintaining conversation context, using an LLM to determine whether human handoff is needed, and sending human handoff and return-to-AI events                                                                       |

## Setup <a href="#setup" id="setup"></a>

Complete all of the following in the MaiAgent admin console, once per conversation platform.

### 1. Create a Web Chat Conversation Platform and Obtain Credentials <a href="#setup-inbox" id="setup-inbox"></a>

API integration uses a **Web Chat** conversation platform. You need:

* **API key**: See [Quickstart](/tech/en/api-integration/quickstart.md) for how to obtain one.
* **Web Chat ID**: <mark style="color:blue;">Customer Service Conversations</mark> → <mark style="color:blue;">Conversation Platforms</mark> → edit the API platform → <mark style="color:blue;">Integration and Login</mark> → the <mark style="color:blue;">API Information</mark> card at the bottom of <mark style="color:blue;">Webhook Settings</mark>. The value is read-only and can be copied; see [Basic Conversation Platform Settings](https://docs.maiagent.ai/build/conversation-settings/platform-settings/basic-settings#web-chat-id) in the user manual.

### 2. Configure an Outgoing Webhook (Receive AI Replies) <a href="#setup-outgoing-webhook" id="setup-outgoing-webhook"></a>

<mark style="color:blue;">Customer Service Conversations</mark> → <mark style="color:blue;">Conversation Platforms</mark> → edit the platform → <mark style="color:blue;">Webhook Settings</mark> → <mark style="color:blue;">Add Webhook</mark>. Select **Outgoing** as the type and enter your reply receiver URL as the endpoint. Select **MaiAgent Standard Format**; if your system requires a fixed JSON structure, select **Custom Template**. See [Webhook](/tech/en/serve/webhook.md) for fields and payloads.

### 3. Enable Human Agent Handoff <a href="#setup-handoff" id="setup-handoff"></a>

On the same conversation platform → <mark style="color:blue;">Human Handoff Settings</mark>. See [Human Handoff Settings](https://docs.maiagent.ai/build/conversation-settings/human-handoff) in the user manual for each field's meaning and the settings screen. When human agents work in your system, use these recommended settings:

| Setting                                                                                                                               | Recommendation when human agents work in your system                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <mark style="color:blue;">Enable Human Agent Handoff</mark>                                                                           | Enable                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| Evaluation rules (<mark style="color:blue;">Role Instructions</mark>) under <mark style="color:blue;">Human Handoff Conditions</mark> | Start with the default rules: the visitor explicitly requests a human, the issue exceeds AI capabilities, the visitor is upset, or account operations require manual verification. Then adapt them to your business, for example by adding keywords or handing off when confidence is low                                                                                                                                                                                                         |
| <mark style="color:blue;">Dispatch Settings</mark> → <mark style="color:blue;">Dispatch Mode</mark>                                   | Select **Handover**. MaiAgent will not assign the conversation to admin console members. It stays queued for your system to take over                                                                                                                                                                                                                                                                                                                                                             |
| <mark style="color:blue;">Dispatch Settings</mark> → <mark style="color:blue;">Queue Timeout</mark>                                   | Check **No Timeout** (available only in Handover mode). The conversation stays queued until you call return-to-AI; MaiAgent will not return it to AI on its own                                                                                                                                                                                                                                                                                                                                   |
| <mark style="color:blue;">Dispatch Settings</mark> → <mark style="color:blue;">Queue Limit</mark>                                     | Set according to your support capacity (1–500). When the queue is full, new human handoff requests are not triggered: the message receives no AI reply, and no `handoff.requested` event is sent                                                                                                                                                                                                                                                                                                  |
| <mark style="color:blue;">Service Hours</mark>                                                                                        | Enable as needed. With <mark style="color:blue;">Display Offline Message</mark>, no human handoff occurs outside service hours: the message receives no AI reply and no `handoff.requested` event is sent (the offline message is sent only to the Web Chat frontend; your system must handle the notice outside service hours). With <mark style="color:blue;">Accept Messages</mark>, the conversation still enters the queue and sends `handoff.requested`, without starting the queue timeout |

When configuring the conversation platform via API, Handover mode with no timeout corresponds to `dispatchMode: "handover"` and `assignmentQueueTimeout: 0`.

{% hint style="danger" %}
**Do not set the queue timeout to 0 in automatic dispatch mode.** The queue timeout for automatic dispatch (the default) accepts only 1–3600 seconds: entering 0 in the UI shows an out-of-range warning, while submitting 0 via API changes it to the default of 300 seconds. If no MaiAgent member takes over within 300 seconds, MaiAgent sends `handoff.queue_timeout` and lets AI resume the conversation, without either the visitor or your agent noticing.
{% endhint %}

{% hint style="warning" %}
Visitor-facing prompts such as <mark style="color:blue;">Handoff Notification Message</mark> and <mark style="color:blue;">Queue Waiting Prompt</mark> are sent to MaiAgent's own Web Chat frontend and **are not sent to your system via Webhook**. When you receive `handoff.requested`, have your system display a prompt such as “Connecting you to a human agent.”
{% endhint %}

### 4. Enable the Human Handoff Webhook <a href="#setup-handoff-webhook" id="setup-handoff-webhook"></a>

On the same tab → the <mark style="color:blue;">Notifications</mark> subtab → enable <mark style="color:blue;">Webhook Notifications (System Integration)</mark> and enter:

* <mark style="color:blue;">Webhook URL</mark>: HTTPS only.
* <mark style="color:blue;">Signing Secret (Optional)</mark> (recommended): When set, each delivery includes an HMAC-SHA256 signature. See [Delivery and Signatures](#handoff-delivery) for verification.

<mark style="color:blue;">Email Notifications</mark> and <mark style="color:blue;">Notification Center Announcements</mark> are sent only when a conversation is assigned to a MaiAgent member. They are unrelated to this integration and can be disabled.

{% hint style="info" %}
The conversation platform administrator enables these three notification switches in the admin console; no separate application is required. See [Human Handoff Notifications](https://docs.maiagent.ai/build/conversation-settings/human-handoff/handoff-notification) in the user manual for the settings screen.
{% endhint %}

## Step 1: Map Visitor Identities <a href="#step-identity" id="step-identity"></a>

Call the identity sync API with the user ID from your system to obtain the visitor's `contactId` in MaiAgent:

```bash
curl -X POST \
  'https://api.maiagent.ai/api/v1/web-chats/{webChatId}/setup-contact-credentials/' \
  -H 'Content-Type: application/json' \
  -d '{ "sourceId": "your-user-id", "name": "Wang Xiaoming" }'
```

```json
{ "contactId": "c0ffee00-1111-2222-3333-444455556666" }
```

Repeated calls with the same `sourceId` return the same `contactId` (idempotent). Store the `sourceId ↔ contactId` mapping in your user data. Use an unguessable value for `sourceId`. See [Contact Identity Sync and Token Updates](/tech/en/serve/contacts/contact-credentials-sync.md) for details.

## Step 2: Create a Conversation <a href="#step-conversation" id="step-conversation"></a>

Create one conversation for each visitor and **reuse it over time**. MaiAgent maintains context per conversation:

```bash
curl -X POST 'https://api.maiagent.ai/api/v1/conversations/' \
  -H 'Authorization: Api-Key <YOUR_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{ "webChat": "<Web Chat ID>", "contact": "<contactId>" }'
```

Record `id` (conversation ID) and `inbox.id` (conversation platform ID, used when configuring Webhooks) from the response. See [Creating Conversations and Messages](/tech/en/api-integration/conversations-and-messages.md) for response fields.

If you want each visit to start a fresh conversation (for example, after a ticket is closed), create a new conversation. The old conversation history remains available.

## Step 3: Send Visitor Messages <a href="#step-send" id="step-send"></a>

```bash
curl -X POST 'https://api.maiagent.ai/api/v1/messages/' \
  -H 'Authorization: Api-Key <YOUR_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{
    "conversation": "<Conversation ID>",
    "content": "I would like to check my order status",
    "attachments": []
  }'
```

A `201` response only means the message was created. **The AI reply is delivered within a few seconds via the Outgoing Webhook** (Step 4); the HTTP response itself does not contain the reply. For attachments, upload them first to obtain attachment objects, then include them in `attachments`. See [Presigned File Upload Mode](/tech/en/api-integration/api_knowledge.md).

## Step 4: Receive AI Replies <a href="#step-receive" id="step-receive"></a>

After creating an AI reply, MaiAgent sends a request to your configured Outgoing Webhook. For example, in the standard format your endpoint receives:

```json
{
  "id": "2491074a-6d23-4289-af45-caa12531420e",
  "type": "outgoing",
  "content": "Your order #12345 is currently in transit and is expected to arrive tomorrow.",
  "created_at": "2026-09-09T10:15:30.123456+00:00",
  "conversation": {
    "id": "8e44604a-8278-4c96-96b5-3e5a0548d738",
    "status": "open",
    "progress_status": "ai_processing",
    "auto_reply_enabled": true,
    "assignee": null
  },
  "contact": { "id": "c0ffee00-1111-2222-3333-444455556666", "name": "Wang Xiaoming" },
  "sender": { "id": "…", "name": "AI Customer Service", "type": "chatbot" },
  "attachments": []
}
```

Your system uses `contact.id` to find the user and `conversation.id` to find the conversation, then converts `content` and attachments to your channel's format and sends them to the visitor. Respond with 2xx. See [Webhook](/tech/en/serve/webhook.md) for all fields, custom templates, and delivery behavior.

{% hint style="info" %}
If you need your own user ID or downloadable attachment URLs directly in the payload, use a **Custom Template** to output `contact.source_id` and `attachment.file.url`. See [Webhook › Custom Templates](/tech/en/serve/webhook.md#custom-template) for examples.
{% endhint %}

## Step 5: AI Determines a Human Handoff Is Needed <a href="#step-handoff" id="step-handoff"></a>

For each incoming visitor message, MaiAgent first uses an evaluation model to determine whether human assistance is needed. If it determines that assistance is needed:

1. The conversation status changes to `queued` and `auto_reply_enabled` becomes `false`. **This message receives no AI reply**, and AI stops replying to subsequent messages as well.
2. MaiAgent sends a `handoff.requested` event to your human handoff Webhook URL.

### Event Payload <a href="#handoff-payload" id="handoff-payload"></a>

See “Event Format” in the API documentation for [Human Handoff Webhook Events](https://docs.maiagent.ai/api/preparation/handoff-webhook) for the field table and JSON examples. Key points for this integration:

* All four event types have exactly the same fields. Route them by `event` (see the [event list](#events) below).
* `conversation.id` and `contact.id` correspond to the conversation and visitor recorded in Steps 1 and 2.
* Deduplicate by event `id` and order by `occurred_at`; the payload contains identifiers only, with no conversation content.
* `assignee` is the MaiAgent member assigned at the time of the event; it is always `null` for `handoff.requested` and when your agents take over.
* Field names and nesting match the message Webhook in Step 4, so the same receiver can handle both sets of deliveries.

### Delivery and Signatures <a href="#handoff-delivery" id="handoff-delivery"></a>

`POST`, `Content-Type: application/json`, with a 10-second timeout. Non-2xx responses or connection failures are **retried every 60 seconds, up to 3 retries**. When a signing secret is configured, requests include `X-MaiAgent-Signature-256: sha256=<hex>`, whose value is the HMAC-SHA256 of the raw request body computed with the secret. See “Signature Verification” and the [Minimal Receiver Example](https://docs.maiagent.ai/api/preparation/handoff-webhook#receiver-example) in the API documentation for [Human Handoff Webhook Events](https://docs.maiagent.ai/api/preparation/handoff-webhook) for verification code and a receiver you can run directly.

### After Your System Receives `handoff.requested` <a href="#after-triggered" id="after-triggered"></a>

* Create a ticket in your system, assign a human agent, and display a handoff prompt to the visitor.
* Your agents then handle visitor messages in your system. **You can stop sending them to MaiAgent**. If you still want MaiAgent to retain the full history, keep calling `POST /messages/`: messages are stored in the conversation and trigger the Incoming Webhook, but no AI replies are generated until the conversation returns to AI.
* Your system sends human agent replies directly to the visitor; they do not need to pass through MaiAgent.

## Step 6: Return to AI When Human Service Ends <a href="#step-return" id="step-return"></a>

When the ticket is resolved, call return-to-AI:

```bash
curl -X PATCH 'https://api.maiagent.ai/api/v1/conversations/{conversationId}/return-to-ai/' \
  -H 'Authorization: Api-Key <YOUR_API_KEY>'
```

The conversation returns to `open`, `auto_reply_enabled` returns to `true`, and MaiAgent sends `handoff.returned_to_ai`. AI resumes replying to subsequent visitor messages. The response is the updated conversation object.

{% hint style="warning" %}
The member who owns the API key must have conversation access permissions for this platform. If you did not select Handover mode and check <mark style="color:blue;">No Timeout</mark> as described in [Setup](#setup-handoff), MaiAgent automatically returns to AI and sends `handoff.queue_timeout` when the <mark style="color:blue;">Queue Timeout</mark> expires. Confirm that this matches your workflow. Conversations queued outside service hours with <mark style="color:blue;">Accept Messages</mark> **do not start the queue timeout**; your system must always call `return-to-ai` to end the handoff.
{% endhint %}

## Scenario B: Visitors on MaiAgent's Conversation Platform, Human Agents in Your System <a href="#scenario-b" id="scenario-b"></a>

Visitors chat with the Agent through MaiAgent's conversation platform, while human agents reply from your customer service system only after handoff. Visitor messages already enter MaiAgent, so your system does not need Steps 1–3. Instead, use message Webhooks to bring messages into your system during human handoff, then use the API to write agent replies back to the conversation for bidirectional sync.

Complete setup on the conversation platform used by visitors: [Enable Human Agent Handoff](#setup-handoff) (Handover mode + No Timeout) and [Enable the Human Handoff Webhook](#setup-handoff-webhook) are the same as Scenario A. For [Message Webhooks](#setup-outgoing-webhook), add both **Incoming** (visitor messages) and **Outgoing** (Agent or human agent replies). Writing human agent replies back and returning to AI require an API key; see [Quickstart](/tech/en/api-integration/quickstart.md) to obtain one.

### Integration Flow <a href="#scenario-b-flow" id="scenario-b-flow"></a>

1. **Detect the start of human handoff**: When you receive `handoff.requested`, open a ticket or create a session in your system using `conversation.id`, and record `contact.id`. The event contains no conversation text. To retrieve the conversation before handoff, use `conversation.id` to call [List Messages](https://docs.maiagent.ai/api/api-reference/dui-hua-he-xun-xi#messages-list) in the API documentation. If you later receive `handoff.assigned`, the conversation has been assigned to a member in the MaiAgent admin console, identified by `assignee.id`.
2. **Receive visitor messages and human agent replies**: Each message Webhook payload includes the current conversation state. Process only messages where `conversation.auto_reply_enabled` is `false` to skip traffic while the Agent is replying automatically. Use `sender.type` to distinguish visitors (`contact`), human agents (`user`), and the Agent (`chatbot`). See [Webhook › Field Definitions](/tech/en/serve/webhook.md#standard-fields).
3. **Write human agent replies back to the conversation**: Call `POST /api/v1/messages/outgoing/` with your API key, passing `conversation` and `content`. MaiAgent sends the message to the visitor's conversation platform. The sender is the member who owns the API key, and that member must have conversation access permissions for the platform. See [Send Proactive Messages](https://docs.maiagent.ai/api/api-reference/dui-hua-he-xun-xi#messages-outgoing-create) in the API documentation for fields.
4. **End human handoff**: After service is complete, call `return-to-ai` (as in [Step 6](#step-return)) to let the Agent take over again. Whether you call the API or a member clicks “Return to AI Agent” in the admin console, you receive `handoff.returned_to_ai`. Use it to close the session in your system.

{% hint style="warning" %}
With the recommended Handover mode + No Timeout settings, conversations remain queued after handoff, with `conversation.progress_status` set to `waiting_for_human`. It changes to `human_serving` only when the conversation is assigned to a MaiAgent admin console member. Use `auto_reply_enabled` to determine whether a human is handling the conversation. Checking only whether `progress_status` is `human_serving` will cause you to miss visitor messages.
{% endhint %}

### Call Examples <a href="#scenario-b-examples" id="scenario-b-examples"></a>

**(1) Message Webhook received during human handoff** (MaiAgent standard format, excerpt): `auto_reply_enabled` set to `false` means a human is handling the conversation; `sender.type` set to `contact` means it is a visitor message.

```json
{
  "id": "9d2f7a10-...",
  "type": "incoming",
  "content": "I want to change my order's delivery address",
  "created_at": "2026-09-09T02:30:00+00:00",
  "conversation": {
    "id": "6c2b7c1e-...",
    "title": "...",
    "type": "individual",
    "status": "queued",
    "progress_status": "waiting_for_human",
    "auto_reply_enabled": false,
    "assignee": null
  },
  "inbox": { "id": "0f9a3d42-...", "name": "Website Support", "channel_type": "web" },
  "contact": { "id": "a4c1f6b3-...", "name": "Wang Xiaoming", "email": null, "phone_number": null },
  "sender": { "id": "a4c1f6b3-...", "name": "Wang Xiaoming", "type": "contact" },
  "attachments": [],
  "metadata": {}
}
```

**(2) Write a human agent reply back to the conversation**: Pass only `conversation` and `content`. The message is sent to the visitor's conversation platform as the member who owns the API key.

```bash
curl -X POST "https://api.maiagent.ai/api/v1/messages/outgoing/" \
  -H "Authorization: Api-Key YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation": "6c2b7c1e-...",
    "content": "Hello, your address has been updated to Xinyi District, Taipei City…"
  }'
```

**(3) Combine all three steps** (Python, `requests`):

```python
import requests

API = "https://api.maiagent.ai/api/v1"
HEADERS = {"Authorization": "Api-Key YOUR_API_KEY"}


def on_message_webhook(payload: dict) -> None:
    conv = payload["conversation"]
    if conv["auto_reply_enabled"]:
        return  # Do not send traffic to the support system while the Agent replies automatically
    if payload["sender"]["type"] == "contact":
        push_to_agent_console(conv["id"], payload["content"])  # Visitor message → your agent interface


def reply(conversation_id: str, text: str) -> None:
    requests.post(f"{API}/messages/outgoing/", headers=HEADERS,
                  json={"conversation": conversation_id, "content": text}, timeout=10).raise_for_status()


def finish(conversation_id: str) -> None:
    requests.patch(f"{API}/conversations/{conversation_id}/return-to-ai/", headers=HEADERS, timeout=10).raise_for_status()
```

### Notes <a href="#scenario-b-notes" id="scenario-b-notes"></a>

* Human agent replies written via API are also sent once through the Outgoing message Webhook (`sender.type` is `user`). Deduplicate by message `id` to avoid displaying them twice in your system.
* `handoff.assigned` is sent again when the conversation is transferred to another member, with `assignee.id` identifying the new owner. Update the existing session using `conversation.id` instead of opening another ticket.
* Message Webhooks and human handoff Webhooks use different verification methods. See [Pre-integration FAQ › Authentication](#faq-auth).

## Event List <a href="#events" id="events"></a>

| `event`                  | Trigger                                                                                                                                                                              | Meaning for your system                                                                                  |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| `handoff.requested`      | AI determines human assistance is needed; the conversation enters the queue and AI stops replying                                                                                    | Your human agents take over                                                                              |
| `handoff.assigned`       | The conversation is assigned to a human agent member in the MaiAgent admin console (a member takes over, transfers it, or is assigned automatically; a transfer sends another event) | You will not receive this event if all human agents work in your system                                  |
| `handoff.returned_to_ai` | You call `return-to-ai`, or a member clicks “Return to AI Agent” in the admin console                                                                                                | AI resumes replying; you can close the ticket                                                            |
| `handoff.queue_timeout`  | The queue timeout expires and MaiAgent automatically returns the conversation to AI                                                                                                  | AI has resumed replying; if your agents are still handling it, this is an anomaly that requires an alert |

Each event has a corresponding `event_type` (`transfer_triggered` / `human_assigned` / `returned_to_ai` / `queue_timeout`) in `GET /api/v1/conversations/{id}/events/` for reconciliation.

{% hint style="info" %}
Events are sent by separate background tasks. **Arrival order is not guaranteed**, and retries may produce duplicates. Deduplicate by event `id`, order by `occurred_at`, and treat `handoff.assigned` as implicitly indicating that handoff has already been triggered.
{% endhint %}

### Query the Current State via API <a href="#query-state" id="query-state"></a>

In addition to Webhooks, you can query who is currently handling a conversation at any time:

```bash
GET https://api.maiagent.ai/api/v1/conversations/{conversationId}/
```

`status` (`open` / `queued` / `resolved`), `autoReplyEnabled`, and `progressStatus` (`ai_processing` / `waiting_for_human` / `human_serving`) are sufficient to reconstruct the state. `GET /api/v1/conversations/{conversationId}/events/` lists human handoff event records (`transfer_triggered`, `queued`, `human_assigned`, `returned_to_ai`, `queue_timeout`, etc.) for reconciliation or recovery.

## Pre-integration FAQ <a href="#kickoff-faq" id="kickoff-faq"></a>

Customer service system vendors usually ask the following questions at kick-off. The answers are collected here and can be used directly as an appendix to the integration document shared by both parties.

### Environments and Paths <a href="#faq-environments" id="faq-environments"></a>

| Environment | Base URL                              |
| ----------- | ------------------------------------- |
| Test        | `https://api-dev.maiagent.ai/api/v1/` |
| Production  | `https://api.maiagent.ai/api/v1/`     |

The paths used by this integration are fixed: `POST /web-chats/{webChatId}/setup-contact-credentials/`, `POST /conversations/`, `POST /messages/`, and `PATCH /conversations/{id}/return-to-ai/`; use `GET /conversations/{id}/` and `GET /conversations/{id}/events/` for reconciliation. Your integration contact will create and provide the organization, API key, and Web Chat in the test environment.

### Authentication <a href="#faq-auth" id="faq-auth"></a>

* **You call MaiAgent**: header `Authorization: Api-Key <key>`. The identity sync API does not require a key; it identifies the caller by `webChatId`.
* **MaiAgent pushes to you**: both are signed with HMAC-SHA256 and use the same header, `X-MaiAgent-Signature-256: sha256=<hex>`, but the signed content differs. The message Webhook (requires a Secret Token) signs "the value of `X-MaiAgent-Timestamp` + `.` + the raw request body"; see [Webhook › Verify the Signature and Timestamp](/tech/en/serve/webhook.md#webhook-signature). The human handoff Webhook signs only the raw request body.

### Timeouts, Retries, and Rate Limits <a href="#faq-limits" id="faq-limits"></a>

| Direction                              | Timeout                                                                  | Retry                                                                                                                                                                                                                                                                                                  |
| -------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| You call `POST /messages/`             | Returns 201 synchronously; a client timeout of 10 seconds is recommended | For 4xx, fix the request first; a 5xx or timeout means the result is uncertain, so reconcile first and do not resend directly                                                                                                                                                                          |
| Message Webhook (MaiAgent → you)       | 30 seconds                                                               | On timeout, connection failure, 5xx, or 429, it is resent once each after about 1, 5, and 15 minutes, up to 3 times; other 4xx responses are not resent. Persist the payload before returning 2xx, and deduplicate by message `id`; see [Webhook › Automatic Retries](/tech/en/serve/webhook.md#retry) |
| Human handoff Webhook (MaiAgent → you) | 10 seconds                                                               | On failure, it retries every 60 seconds, up to 3 times; deduplicate by event `id`                                                                                                                                                                                                                      |

`POST /messages/` is not guaranteed to be idempotent: the server may have already created the message while the response was lost in transit. Confirm the result first through the conversation record and your own send log; if you cannot confirm it, resending directly may create a duplicate message and trigger the AI reply again.

Each API key can be configured with a per-minute request limit (RPM), which is unlimited by default. Before going live, provide your estimated traffic so your integration contact can set it.

### Source IP Allowlist <a href="#faq-egress" id="faq-egress"></a>

The outbound IPs MaiAgent uses to push Webhooks are fixed. If your receiving endpoint requires an allowlist, ask your integration contact for the latest lists for the production and test environments. MaiAgent has no outbound restrictions on your receiving endpoint's domain.

### Conversation Session Mapping <a href="#faq-session" id="faq-session"></a>

The key is the contact's `sourceId` (we recommend `{platform}:{your user ID}` to avoid collisions across channels). Calling the identity sync API repeatedly with the same `sourceId` returns the same `contactId`. Once a conversation is created, it has **no lifetime limit**: you can keep using the same conversation ID for a long time to preserve context, or create a new conversation for the same contact after closing the case to start over.

### Human Handoff Decision Logic <a href="#faq-handoff-logic" id="faq-handoff-logic"></a>

A decision model judges based on the most recent N messages of the conversation. The default rules are: the visitor explicitly asks for a human, the question is beyond what the AI can handle, the visitor is upset, or the request involves an account operation that needs human verification. You can adjust the rules in natural language in the <mark style="color:blue;">role instructions</mark>, adding conditions such as keywords or handing off whenever confidence is low; you can also set <mark style="color:blue;">service hours</mark> to decide whether to hand off outside those hours. The decision result is the `handoff.requested` event.

### Multi-part Replies and Message Formats <a href="#faq-message-format" id="faq-message-format"></a>

Each AI reply is pushed as one message Webhook (one request per message) with no batching; a single reply can contain multiple attachments. If your system requires an array format, use a custom template to output a single-element array. Text (Markdown) and attachments (images / videos / audio / files) are supported as standard; channel-specific formats such as buttons, cards, and quick replies are outside the standard scope, so raise your requirements at kick-off for separate evaluation.

### Attachment URLs and Lifetime <a href="#faq-attachments" id="faq-attachments"></a>

Attachments in AI replies are output as `attachment.file.url` through a custom template. It is a signed URL that is **valid for 1 hour**, so fetch and store the file immediately after receiving it. For attachments uploaded by visitors, your system first obtains a downloadable URL and passes it in `attachments[]` when sending the message.

### Errors and Usage Queries <a href="#faq-observability" id="faq-observability"></a>

The request, response, and status code of every message Webhook push can be viewed in the <mark style="color:blue;">Webhook</mark> tab of the Agent page in the MaiAgent console. Conversation state and human handoff events can be queried for reconciliation with the conversation API and the event record API; AI usage is available under <mark style="color:blue;">Usage Statistics</mark> in the console. Failed human handoff Webhook pushes go to MaiAgent's monitoring alerts.

## Integration Checklist <a href="#checklist" id="checklist"></a>

* [ ] Console: Outgoing Webhook is configured and can receive AI replies
* [ ] Console: Human agent handoff is enabled; the dispatch mode for external human agents is <mark style="color:blue;">Claim mode</mark>, with <mark style="color:blue;">No timeout</mark> checked
* [ ] Console: The human handoff Webhook is enabled, the HTTPS endpoint is reachable, and signature verification passes
* [ ] Your system: The `sourceId ↔ contactId ↔ conversationId` mapping is persisted
* [ ] Your system: `handoff.requested` → create a case and show a handoff notice; `handoff.returned_to_ai` → close the case; `handoff.queue_timeout` → alert or close the case
* [ ] Your system: Call `return-to-ai` when the human service ends
* [ ] Both Webhook endpoints are idempotent: human handoff events and message pushes each use their own `id`
* [ ] Scenario B: The message Webhook has both Incoming and Outgoing directions added, and uses `auto_reply_enabled` to determine whether a human is handling the conversation
* [ ] If your network requires a source IP allowlist, ask your integration contact for MaiAgent's outbound IPs

## Related Pages <a href="#related" id="related"></a>

* [Human Handoff Webhook Events](https://docs.maiagent.ai/api/preparation/handoff-webhook) (API docs): Full event specification, signature verification, and ready-to-run receiver examples
* [Human Handoff Settings for Customer Service](https://docs.maiagent.ai/build/conversation-settings/human-handoff) (User Manual): Instructions for console fields such as handoff conditions, service hours, and dispatch settings
* [Webhook](/tech/en/serve/webhook.md): Message push format, custom templates, and managing Webhooks via API
* [Genesys Cloud Integration](/tech/en/serve/customer-service-system-integration/genesys-cloud-integration.md): Integrate through Genesys Cloud Open Messaging, with built-in message format, OAuth, and signature verification
* [Create Conversations and Messages](/tech/en/api-integration/conversations-and-messages.md): The flow for creating conversations and sending messages, with fields linked to the API docs
* [Contact Identity Sync and Token Refresh](/tech/en/serve/contacts/contact-credentials-sync.md): Full specification of `setup-contact-credentials`
* [Presigned File Upload Mode](/tech/en/api-integration/api_knowledge.md): Attachment upload


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.maiagent.ai/tech/en/serve/customer-service-system-integration.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
