Skip to main content

UC-009 — Two-Way Conversation

FieldValue
IDUC-009
GoalHandle two-way conversations with customers
ChannelWhatsApp / RCS
Complexity⭐⭐⭐ Advanced
Estimated time20 minutes
APIs involvedPOST /api/partner-gateway/v1/webhooks/delivery-status, GET /api/partner-gateway/v1/inbox/conversations, GET /api/partner-gateway/v1/inbox/conversations/{chatId}/messages, POST /api/partner-gateway/v1/inbox/conversations/reply, PATCH /api/partner-gateway/v1/inbox/conversations/{chatId}/read, PATCH /api/partner-gateway/v1/inbox/conversations/{chatId}/archive

Real-world scenarios​

  • TechStore — Customer support chat: A customer writes on WhatsApp to ask about an order; the system receives the message, assigns it to an operator and replies with the shipping status.
  • Studio Dentistico Bianchi — Booking confirmation by reply: After an appointment reminder is sent, the patient confirms or asks to move the visit by replying to the message.
  • FashionOutlet — Post-sale feedback collection: A post-purchase survey asks the customer to reply with a score from 1 to 5; the system collects and catalogs the replies automatically.

Prerequisites​

Before you begin, make sure you have:

Interaction flow​

Step-by-step guide​

Step 1 — Configure the webhook for inbound messages​

First, register a webhook to receive incoming messages. Despite its name, the delivery-status webhook receives every event of your account: DELIVERY, READ and INBOUND. See the Webhooks guide for the full configuration.

curl -X POST "https://api.qlara.ai/api/partner-gateway/v1/webhooks/delivery-status" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"callbackUrl": "https://api.techstore.it/webhooks/qlara"
}'

Response: 201 Created

{
"companyId": 189,
"callbackUrl": "https://api.techstore.it/webhooks/qlara"
}
One webhook per account

Each account has a single callback URL, shared by SMS, RCS and WhatsApp. If a webhook is already configured, the call answers 409 Conflict and leaves the existing one in place: check it with GET /webhooks/delivery-status and change the URL with PUT on the same path. The URL must be HTTPS on port 443 or 8443 and publicly reachable, otherwise the call answers 400.

Step 2 — Receive an inbound message​

When a customer sends a message, your endpoint receives a payload like this one. INBOUND events exist for RCS and WhatsApp only.

Inbound TEXT:

{
"eventType": "INBOUND",
"channel": "WHATSAPP",
"messageId": "msg-wa-in-001",
"source": "+393471234567",
"destination": "+393209998877",
"receivedDate": "2026-04-09T11:15:22+02:00",
"messageType": "TEXT",
"text": "Buongiorno, vorrei sapere lo stato del mio ordine ORD-2026-1584"
}

Inbound IMAGE:

{
"eventType": "INBOUND",
"channel": "WHATSAPP",
"messageId": "msg-wa-in-002",
"source": "+393471234567",
"destination": "+393209998877",
"receivedDate": "2026-04-09T11:16:05+02:00",
"messageType": "IMAGE",
"mediaKey": "b394ab72/efd4987c/39b4879f6fed01f0d622453be1488c93"
}
FieldDescription
eventTypeAlways INBOUND for a message written by the end user
channelWHATSAPP or RCS
messageIdIdentifier of the message in the conversation
sourceThe end user's phone number
destinationYour WhatsApp number (WhatsApp) or your agent id (RCS)
receivedDateWhen the message was received. INBOUND events have no eventDate
messageTypeTEXT for text messages; media messages (e.g. IMAGE) carry a mediaKey
textText of the message
mediaKeyKey of the attachment, present for media messages

Null fields are left out of the payload, so a text message has no mediaKey.

Download media

To download an attached media file, use GET /api/files/{mediaKey}?expireMinutes=180. The response contains a temporary pre-signed URL. See UC-026: Download Media from Inbound Messages.

Step 3 — List the conversations​

Retrieve the conversations, keeping only those with unread messages:

curl -X GET "https://api.qlara.ai/api/partner-gateway/v1/inbox/conversations?unreadOnly=true" \
-H "X-Api-Key: YOUR_API_KEY"

Response:

[
{
"id": 1042,
"hasUnreadMessages": true,
"userName": "Marco Bianchi",
"phoneNumber": "+393471234567",
"unreadCount": 2,
"lastMessageText": "Buongiorno, vorrei sapere lo stato del mio ordine ORD-2026-1584",
"lastMessageDate": "2026-04-09 09:15:22.041+0000",
"socialMedia": "WHATSAPP",
"companyProfileName": "TechStore Italia",
"companyPhoneNumber": "+393209998877",
"whatsappWindowExpire": "2026-04-10 09:15:22.041+0000"
}
]

Step 4 — Get the conversation messages​

Retrieve the full thread of a conversation:

curl -X GET "https://api.qlara.ai/api/partner-gateway/v1/inbox/conversations/1042/messages" \
-H "X-Api-Key: YOUR_API_KEY"

Response:

{
"profile": {
"userName": "Marco Bianchi",
"profilePicture": null,
"additionalInfo": "+393471234567"
},
"messages": [
{
"id": 5001,
"text": "Buongiorno, vorrei sapere lo stato del mio ordine ORD-2026-1584",
"isRead": false,
"isUserReply": true,
"createdAt": "2026-04-09 09:15:22.041+0000",
"attachments": [],
"status": "DELIVERED"
}
]
}

isUserReply: true marks a message written by the contact, false one of your replies. A chatId that does not exist or does not belong to your account is answered 404.

Step 5 — Reply to the conversation​

Send a reply within the existing conversation:

curl -X POST "https://api.qlara.ai/api/partner-gateway/v1/inbox/conversations/reply" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chatId": 1042,
"message": "Buongiorno Marco! Il suo ordine ORD-2026-1584 è in fase di spedizione. Riceverà il tracking entro oggi pomeriggio."
}'

Response: 202 Accepted

chatId is required. You can also send replyTo (the id of the message you are quoting) and attachments (a list of {"mediaId": …} from your media library). The reply goes out on the conversation's channel; track its delivery through the DELIVERY webhook event.

24-hour window

On WhatsApp, free-form replies are only possible within 24 hours of the customer's last message. Check the whatsappWindowExpire field of the conversation. Outside the window the reply can still be accepted (202) but is not delivered: the DELIVERY event carries statusCode: 12 (conversation closed). After the window expires, use a Meta-approved template. See the WhatsApp guide.

Step 6 — Mark as read and archive​

Once you have handled the request, mark the conversation as read:

curl -X PATCH "https://api.qlara.ai/api/partner-gateway/v1/inbox/conversations/1042/read" \
-H "X-Api-Key: YOUR_API_KEY"

Response: 204 No Content

When the conversation is over, archive it:

curl -X PATCH "https://api.qlara.ai/api/partner-gateway/v1/inbox/conversations/1042/archive" \
-H "X-Api-Key: YOUR_API_KEY"

Response: 204 No Content

Restore

An archived conversation can be restored at any time with PATCH /conversations/{chatId}/unarchive. If the contact writes again, the conversation typically comes back to the active inbox on its own.

Conversation filter parameters​

ParameterTypeDescription
channelIdsstringComma-separated channel IDs (e.g. WHATSAPP-123,RCS-456)
dateFromstringISO date: only conversations updated after this date
orderBystringSort field with a +/- prefix (e.g. -lastMessageDate)
sectionstringInbox section, by assignment state (e.g. only conversations assigned to you, or unassigned ones)
unreadOnlybooleanIf true, returns only conversations with unread messages

Message pagination​

For long threads, use the idFrom (message id greater than or equal to) and idTo (message id less than or equal to) parameters:

curl -X GET "https://api.qlara.ai/api/partner-gateway/v1/inbox/conversations/1042/messages?idFrom=5000&idTo=5050" \
-H "X-Api-Key: YOUR_API_KEY"
Behind the scenes

When a customer sends a message, the carrier (Meta for WhatsApp, Google for RCS) delivers it to the Qlara platform, which:

  1. Creates or updates the conversation -- If the contact already has an open conversation on the same channel, the message is added to the existing thread. Otherwise a new one is created.
  2. Notifies you via webhook -- The INBOUND payload is sent to your registered callback URL. Your server must answer with any 2xx within 5 seconds. Failed deliveries are retried, so the same event can arrive more than once: deduplicate on eventType + messageId.
  3. Manages the window -- On WhatsApp, the platform automatically moves the 24-hour window expiry forward each time the customer writes.
  4. Routes the reply -- When you send a reply with POST /conversations/reply, the platform automatically sends it on the same channel as the original conversation.

Expected result​

AspectDetail
Action completedInbound message received, reply sent, conversation archived
Channel usedWhatsApp / RCS
Delivery confirmationVia webhook (statusCode: 3) within 5-60 sec

Common errors​

ProblemProbable causeSolution
HTTP 401Missing or invalid API KeyCheck X-Api-Key header
HTTP 409 on POST /webhooks/delivery-statusA webhook is already configured for the accountCheck it with GET /webhooks/delivery-status; change the URL with PUT on the same path
HTTP 400 on POST /webhooks/delivery-statusThe callback URL is not a public HTTPS URL on port 443 or 8443Use a publicly reachable HTTPS URL, without credentials or fragment
HTTP 400 on replyMalformed body or missing chatIdSend chatId (required) and message
HTTP 404 — Conversation not foundThe chatId does not exist or does not belong to your accountGet a valid chatId from GET /inbox/conversations
Reply accepted but not deliveredWhatsApp 24h window has closed (DELIVERY event with statusCode: 12)Check whatsappWindowExpire; use a Meta-approved template instead
No INBOUND webhook receivedWebhook not configured, or endpoint unreachable or not answering 2xx within 5 secondsVerify the registration with GET /webhooks/delivery-status and check your endpoint

Next steps​