WhatsApp chat API
One webhook takes a guest's message and answers with the replies to send, shaped like WhatsApp text, button and list messages. The simulator uses it, and so can curl or a test runner.
Endpoint
POST /api/whatsapp at https://restaurant-test-preview.yomox.app/api/whatsapp, JSON or form-encoded. No sign-in and no CSRF token are needed. Limit: 120 requests a minute per IP address (then 429).
| Field | Rule | Meaning |
|---|---|---|
from | required, +27, 27 or 0 and 9 digits | The guest's WhatsApp number; bookings and the loyalty card belong to it |
message_id | required, 1-128 of A-Z a-z 0-9 . _ : = - | Unique id of this message, used to answer retries safely |
text | required, up to 1000 characters | What the guest typed, or the title of the button or row they tapped |
reply_id | optional, 1-64 of A-Z a-z 0-9 . _ : - | The id of the button or list row tapped |
name | optional, up to 60 characters | The guest's WhatsApp profile name, used in the greeting |
Replies
The answer is JSON: {"to": "+27...", "messages": [...]}, with Cache-Control: no-store. Each message is one of three types. Text uses WhatsApp formatting, such as *bold*.
| Type | Shape | Limits |
|---|---|---|
text | {type, text} | Body up to 1024 characters |
buttons | {type, text, buttons: [{id, title}]} | Up to 3 buttons, titles up to 20 characters |
list | {type, text, button, rows: [{id, title, description}]} | Up to 10 rows, titles up to 24 and descriptions up to 72 characters, list button up to 20 |
curl -s -X POST https://restaurant-test-preview.yomox.app/api/whatsapp -H 'Content-Type: application/json' \
-d '{"from":"+27820000000","message_id":"demo-1","text":"hi","name":"Thandi"}'
{"to":"+27820000000","messages":[{"type":"list",
"text":"Hello Thandi, welcome to *Fynbos Kitchen* in Stellenbosch.\n\nWhat would you like to do?",
"button":"Choose","rows":[
{"id":"food","title":"See our menu","description":"Starters, mains, desserts and drinks"},
{"id":"book","title":"Book a table","description":"Lunch or dinner, Tuesday to Sunday"},
{"id":"bookings","title":"My bookings","description":"See or cancel a booking"},
{"id":"loyalty","title":"Loyalty card","description":"Collect stamps for a free main course"},
{"id":"info","title":"Hours and location","description":"Find us in Stellenbosch"}]}]}
# Tap "Loyalty card" in that list
curl -s -X POST https://restaurant-test-preview.yomox.app/api/whatsapp -H 'Content-Type: application/json' \
-d '{"from":"+27820000000","message_id":"demo-2","text":"Loyalty card","reply_id":"loyalty"}'
{"to":"+27820000000","messages":[{"type":"buttons",
"text":"*Fynbos Kitchen loyalty card*\n\nEvery booking you make earns a stamp. ...",
"buttons":[{"id":"join","title":"Join loyalty card"},{"id":"main","title":"Main menu"}]}]}
How input is read
- A
reply_idwins. Without one, the text is matched against the current options by number (2) or by title (book a table, any case). hi,hello,menu,startorrestartgo back to the main menu from anywhere.helpor?explains how to reply.- Anything else answers "Sorry, I didn't understand that." and shows the same options again.
- After 30 minutes without a message, the next message starts again at the main menu.
Retries
WhatsApp can deliver the same message twice. The same message_id from the same number gets the stored answer again, and nothing is booked twice. The same message_id from another number gets 409 "That message_id has already been used." A request that breaks a rule gets 422 with a JSON list of errors.
Menu map
- See our menu: starters, mains, desserts and drinks with prices.
- Book a table: a day in the next week (closed Mondays), then a seating with seats left, then the party size (1 to 12; bigger groups are asked to phone), then Confirm. The answer gives a reference such as FBK-7KQ2XD.
- My bookings: upcoming bookings for this number. Pick one to see it, then cancel it or keep it.
- Loyalty card: join, see your stamps, and redeem a free main course.
- Hours and location: address, phone number and opening times.
Seatings
| Time | Seats |
|---|---|
| 12:30 | 30 |
| 18:00 | 40 |
| 19:30 | 40 |
| 21:00 | 12 |
Seats are taken inside one database transaction, so two guests can never book the last seats twice. Cancelling a booking puts its seats back straight away.
Loyalty card rules
- Joining is free. The loyalty card number (LC-xxxxxx) is linked to the WhatsApp number.
- Every confirmed booking earns 1 stamp. Joining straight after a booking stamps that booking too.
- Cancelling a booking takes its stamp off again.
- 8 stamps can be redeemed for a voucher (FREE-xxxxxx) for any main course, valid for 30 days. Redeeming uses 8 stamps.
Meals are paid at the restaurant. The chat never asks for credit card details.
Test data
+27 82 000 0808already has loyalty card LC-DEMO08 with 8 stamps, so you can try redeeming a free main.- The 21:00 seating has only 12 seats, so you can try asking for more than are left.
- Any other valid South African number starts with no bookings and no loyalty card.