Wow! Build your WhatsApp integration. Start free. Register Now
Appointment booking · WhatsApp API use case

Let clients pick a visit time in WhatsApp from a list

Clients finish booking faster when they tap one clear time instead of typing back and forth. When your booking flow reaches the step that needs a time choice, your backend can POST an interactive list to the client chat through Whapi.Cloud. This use case shows integrators how to implement that flow through the WhatsApp API.

type list with up to ten slot rows Choice arrives as list_reply on messages.post
Developer dashboard beside a phone showing a WhatsApp booking chat with an open interactive list of appointment times.
Use case

When to use this flow

Open slots already live in your scheduler

Use this flow when your own_backend already knows which visit times are free and you only need the client to pick one in chat.

Booking stays in a private WhatsApp chat

Use this flow when the client books in a direct_chat with your business number, not in a group thread or a channel post.

You want a structured tap instead of free text

Use this flow when typing “Tuesday 3pm” leads to mistakes and you prefer a list row with a stable slot id your backend can book.

WhatsApp chat on a phone showing an interactive list of visit times and the client’s selected slot highlighted as the sent reply.
Customer experience

The client taps one open slot

Before automation, clients guess times in free text and staff re-check the calendar by hand.

After the list arrives, the client opens the menu, taps one row, and sees their choice sent back in the same chat. Integrators build this result with the WhatsApp API.

Workflow

From open slots to a captured id

1

Booking needs a time choice

Your scheduling logic in own_backend reaches the step where the client must pick among current free visit times.

2

Send the slot list message

POST /messages/interactive

POST to /messages/interactive with type list, header/body/footer text, and action.list.sections rows (title, id, description) built from open slots—typically up to ten rows in one section. Map each row id to a slot key your backend can book (for example slot-2026-04-02-10-00).

Need the client tap on your webhook? After the list is sent, listen for the incoming reply on your channel webhook. Learn about delivery status →

Prerequisites

Before you run the flow

Connect the WhatsApp number that will send booking lists to Whapi.Cloud, then use that channel API token on sendMessageInteractive. Incoming list_reply events require a webhook URL on the same channel.

Optional — go deeper for this flow

API implementation

Build the list request step by step

One POST delivers the menu. Set type to list, fill header, body, and footer objects, then action.list with a button label and sections[].rows—each row needs title, id, and optional description tied to a free slot from your backend.

Get your API token for free after you register on whapi.cloud, and use that token in the code instead of Your_Token from the examples.

Send the slot list message

Send an interactive list (type list) to the client to field with up to ten slot rows; the client opens the menu via action.list.label and taps one row.

curl --request POST \
     --url https://gate.whapi.cloud/messages/interactive \
     --header 'accept: application/json' \
     --header 'authorization: Bearer Your_Token' \
     --header 'content-type: application/json' \
     --data '
{
  "to": "447123456789",
  "type": "list",
  "header": {
    "text": "Choose a visit time"
  },
  "body": {
    "text": "Tap View times and pick an open slot for your visit."
  },
  "footer": {
    "text": "Clinic bookings via WhatsApp"
  },
  "action": {
    "list": {
      "label": "View times",
      "sections": [
        {
          "title": "Available slots",
          "rows": [
            {
              "id": "slot-2026-04-02-10-00",
              "title": "Wed 10:00",
              "description": "Open \u00b7 Dr. Lee"
            },
            {
              "id": "slot-2026-04-02-11-30",
              "title": "Wed 11:30",
              "description": "Open \u00b7 Dr. Lee"
            },
            {
              "id": "slot-2026-04-02-14-00",
              "title": "Wed 14:00",
              "description": "Open \u00b7 Dr. Lee"
            }
          ]
        }
      ]
    }
  }
}
'
$curl = curl_init();

curl_setopt_array($curl, [
  CURLOPT_URL => "https://gate.whapi.cloud/messages/interactive",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => "{\"to\": \"447123456789\", \"type\": \"list\", \"header\": {\"text\": \"Choose a visit time\"}, \"body\": {\"text\": \"Tap View times and pick an open slot for your visit.\"}, \"footer\": {\"text\": \"Clinic bookings via WhatsApp\"}, \"action\": {\"list\": {\"label\": \"View times\", \"sections\": [{\"title\": \"Available slots\", \"rows\": [{\"id\": \"slot-2026-04-02-10-00\", \"title\": \"Wed 10:00\", \"description\": \"Open \\u00b7 Dr. Lee\"}, {\"id\": \"slot-2026-04-02-11-30\", \"title\": \"Wed 11:30\", \"description\": \"Open \\u00b7 Dr. Lee\"}, {\"id\": \"slot-2026-04-02-14-00\", \"title\": \"Wed 14:00\", \"description\": \"Open \\u00b7 Dr. Lee\"}]}]}}}",
  CURLOPT_HTTPHEADER => [
    "accept: application/json",
    "authorization: Bearer Your_Token",
    "content-type: application/json"
  ],
]);

$response = curl_exec($curl);
curl_close($curl);
echo $response;
import requests

url = "https://gate.whapi.cloud/messages/interactive"
payload = {
    "to": "447123456789",
    "type": "list",
    "header": {
        "text": "Choose a visit time"
    },
    "body": {
        "text": "Tap View times and pick an open slot for your visit."
    },
    "footer": {
        "text": "Clinic bookings via WhatsApp"
    },
    "action": {
        "list": {
            "label": "View times",
            "sections": [
                {
                    "title": "Available slots",
                    "rows": [
                        {
                            "id": "slot-2026-04-02-10-00",
                            "title": "Wed 10:00",
                            "description": "Open \u00b7 Dr. Lee"
                        },
                        {
                            "id": "slot-2026-04-02-11-30",
                            "title": "Wed 11:30",
                            "description": "Open \u00b7 Dr. Lee"
                        },
                        {
                            "id": "slot-2026-04-02-14-00",
                            "title": "Wed 14:00",
                            "description": "Open \u00b7 Dr. Lee"
                        }
                    ]
                }
            ]
        }
    }
}
headers = {
    "accept": "application/json",
    "authorization": "Bearer Your_Token",
    "content-type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)
print(response.text)
const response = await fetch('https://gate.whapi.cloud/messages/interactive', {
  method: 'POST',
  headers: {
    accept: 'application/json',
    authorization: 'Bearer Your_Token',
    'content-type': 'application/json',
  },
  body: JSON.stringify({"to": "447123456789", "type": "list", "header": {"text": "Choose a visit time"}, "body": {"text": "Tap View times and pick an open slot for your visit."}, "footer": {"text": "Clinic bookings via WhatsApp"}, "action": {"list": {"label": "View times", "sections": [{"title": "Available slots", "rows": [{"id": "slot-2026-04-02-10-00", "title": "Wed 10:00", "description": "Open \u00b7 Dr. Lee"}, {"id": "slot-2026-04-02-11-30", "title": "Wed 11:30", "description": "Open \u00b7 Dr. Lee"}, {"id": "slot-2026-04-02-14-00", "title": "Wed 14:00", "description": "Open \u00b7 Dr. Lee"}]}]}}}),
});

console.log(await response.text());
OkHttpClient client = new OkHttpClient();

MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\"to\": \"447123456789\", \"type\": \"list\", \"header\": {\"text\": \"Choose a visit time\"}, \"body\": {\"text\": \"Tap View times and pick an open slot for your visit.\"}, \"footer\": {\"text\": \"Clinic bookings via WhatsApp\"}, \"action\": {\"list\": {\"label\": \"View times\", \"sections\": [{\"title\": \"Available slots\", \"rows\": [{\"id\": \"slot-2026-04-02-10-00\", \"title\": \"Wed 10:00\", \"description\": \"Open \\u00b7 Dr. Lee\"}, {\"id\": \"slot-2026-04-02-11-30\", \"title\": \"Wed 11:30\", \"description\": \"Open \\u00b7 Dr. Lee\"}, {\"id\": \"slot-2026-04-02-14-00\", \"title\": \"Wed 14:00\", \"description\": \"Open \\u00b7 Dr. Lee\"}]}]}}}");
Request request = new Request.Builder()
  .url("https://gate.whapi.cloud/messages/interactive")
  .post(body)
  .addHeader("accept", "application/json")
  .addHeader("authorization", "Bearer Your_Token")
  .addHeader("content-type", "application/json")
  .build();

Response response = client.newCall(request).execute();
using RestSharp;

var options = new RestClientOptions("https://gate.whapi.cloud/messages/interactive");
var client = new RestClient(options);
var request = new RestRequest("");
request.AddHeader("accept", "application/json");
request.AddHeader("authorization", "Bearer Your_Token");
request.AddHeader("content-type", "application/json");
request.AddJsonBody(System.Text.Json.JsonSerializer.Deserialize<object>(@"{\"to\": \"447123456789\", \"type\": \"list\", \"header\": {\"text\": \"Choose a visit time\"}, \"body\": {\"text\": \"Tap View times and pick an open slot for your visit.\"}, \"footer\": {\"text\": \"Clinic bookings via WhatsApp\"}, \"action\": {\"list\": {\"label\": \"View times\", \"sections\": [{\"title\": \"Available slots\", \"rows\": [{\"id\": \"slot-2026-04-02-10-00\", \"title\": \"Wed 10:00\", \"description\": \"Open \\u00b7 Dr. Lee\"}, {\"id\": \"slot-2026-04-02-11-30\", \"title\": \"Wed 11:30\", \"description\": \"Open \\u00b7 Dr. Lee\"}, {\"id\": \"slot-2026-04-02-14-00\", \"title\": \"Wed 14:00\", \"description\": \"Open \\u00b7 Dr. Lee\"}]}]}}}"));
var response = await client.PostAsync(request);

Console.WriteLine("{0}", response.Content);
package main

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

func main() {
	body := []byte(`{"to": "447123456789", "type": "list", "header": {"text": "Choose a visit time"}, "body": {"text": "Tap View times and pick an open slot for your visit."}, "footer": {"text": "Clinic bookings via WhatsApp"}, "action": {"list": {"label": "View times", "sections": [{"title": "Available slots", "rows": [{"id": "slot-2026-04-02-10-00", "title": "Wed 10:00", "description": "Open \u00b7 Dr. Lee"}, {"id": "slot-2026-04-02-11-30", "title": "Wed 11:30", "description": "Open \u00b7 Dr. Lee"}, {"id": "slot-2026-04-02-14-00", "title": "Wed 14:00", "description": "Open \u00b7 Dr. Lee"}]}]}}}`)
	req, _ := http.NewRequest("POST", "https://gate.whapi.cloud/messages/interactive", bytes.NewReader(body))
	req.Header.Set("accept", "application/json")
	req.Header.Set("authorization", "Bearer Your_Token")
	req.Header.Set("content-type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
Incoming choice

Read the slot from list_reply

Delivery status webhooks still apply to the outbound list. The booking decision arrives separately on messages.post when the client taps a row—look for reply.type list_reply and reply.list_reply.id (plus title/description) to map back to your slot row id.

  • Wire your channel webhook before testing taps; the payload includes event.type messages and event.event post.

  • Match reply.list_reply.id to the row id you sent (Whapi may prefix ids such as ListV3:r1—normalize in own_backend).

  • If the slot is no longer free when the webhook arrives, do not write the calendar; resend an updated list or hand off to staff per your failure_mode.

What to read on delivery and webhooks

Contact our support if something went wrong
When things break

Handle failures at each step

If sendMessageInteractive fails after your booking trigger, the client never sees the slot list—retry or alert staff before you assume a time was chosen.

When the list never reaches the client

A failed send, a forbidden recipient, or a missing media reference leaves the client without rows to tap. Retry with valid parameters, confirm the to chat id, and fall back to manual scheduling if the slot window closes. Contact Whapi support if sends fail with repeated 401 or 500 responses on a healthy channel.

Contact our support if something went wrong

Troubleshoot by API step

Status What it means What to do
400 Wrong request parameters Fix list payload shape—type list, body.text, action.list.label, and at least one section row with title and id per OpenAPI.
401 Need channel authorization for send message Send the channel Bearer token; confirm the number is still paired in the dashboard.
402 Trial version limit exceeded
403 It is forbidden to send to this group/recipient Confirm the recipient is allowed for this channel and the chat id is correct.
429 Too many requests Back off and retry; avoid burst resends of the same slot list.
500 Internal Error
FAQ

List slots and webhook ids

Plan for up to ten rows total across your sections—enough for a same-day or near-term menu. If you have more open slots, paginate with a new list after the client picks a date or ask own_backend to narrow the set before sending.
Set type to list, provide header, body, and footer text objects, and nest action.list with label plus sections[].rows (each row needs title, id, and optional description). Point to the client phone in to—the same shape as the operator reference curl for /messages/interactive.
On messages.post, read reply.list_reply.id and reply.list_reply.title. Map id back to the row id you assigned when building the list, then book or hold that slot in own_backend.
Treat it as your catalog failure_mode—resend an updated list, expire stale row ids, or route the chat to staff. Do not confirm a visit time in your calendar without a fresh list_reply or a manual check.
Supported WhatsApp Features

Everything you need to build WhatsApp automation

Whapi.Cloud gives you access to a complete set of WhatsApp features — messaging, media, groups, channels, webhooks and more. Build bots, automations and integrations without feature limitations.

Chosen by developers

Trusted by 3,300+ developers and SaaS teams

Developers worldwide rely on Whapi.Cloud to build automations, bots, CRM integrations, bulk messaging tools and advanced WhatsApp workflows. Our infrastructure processes millions of API requests with high stability — and grows every month.

Icon of feature

5,5K+

active API channels

Icon of feature

21M+

API requests processed monthly

“Everything is simple, intuitive and easy to navigate. The docs are clear, and all the info I need is right on the screen. Great job.”

roberto k. / Web Developer

“We connected WhatsApp to our workflows using n8n in just a few minutes. No code, no complications- everything worked on the first try.”

Martin p. / Developer

“Whapi.Cloud became a key part of our customer communication. We automated product updates, order notifications and support messages without needing Meta templates or approvals”

Oscar T. / CTO

Decorative bubble