Cancelling Appointments via Postback
Overview
When the system sends an appointment confirmation to a LINE user, that message usually includes
a "cancel appointment" button. The button embeds postback data in the form
appt_cancel:{bookingId}, so when the user taps it, LINE delivers a postback event to this
webhook.
webhook-go's job is to translate that postback into a cancellation command and push it onto
the booking_notification queue, where worker-go performs the actual cancellation in the
database and sends the follow-up notifications.
Although it is a small feature, it warrants its own page: it is the only path in the project
that publishes to the booking_notification queue, and it is a good example of postback
routing with a prefix hardcoded in the source.
Business Flow
-
LINE delivers an event with
type: "postback"toPOST /api/line/:id. -
ProcessLinehas already loadedwebhook_config; a config must exist andmboxEnabledmust be"1".:::note Important caveat The entire postback block is wrapped in the
mboxEnabled == "1"condition, which means an OA without mbox enabled cannot cancel appointments through this button. The postback instead falls through to the default path and is published toline_webhook. This mirrors the original NestJS behaviour. ::: -
Read
postback.datafrom the first postback event. -
If it does not start with
mbox_team:but does start withappt_cancel:, this path is taken. -
Take everything after the first colon and parse it as an int64. On a parse failure, the service logs the error
processLine: appt_cancel invalid bookingIdand returns without publishing and without falling through to the default path. This guard is deliberate — it avoids a silent parse that would yield 0. -
Publish bare JSON to the
booking_notificationqueue:
{
"type": "cancel",
"bookingId": 12345,
"userId": "LINE user id from event.source.userId",
"lineOaId": 123
}
- Return immediately without publishing to
line_webhook, since the bot has no need to see this postback.
Key Files & Functions
This feature is reached through the POST /api/line/:id endpoint (see
LINE Webhook Gateway) and has no route of its own.
| File | Relevant part |
|---|---|
internal/line/service.go | Service.ProcessLine, in the block checking the appt_cancel: prefix |
internal/line/service.go | The bookingCancelPayload type, holding Type, BookingID, UserID, and LineOaID |
internal/line/service.go | splitSecond(data) extracts the second segment after the colon using SplitN |
internal/line/service.go | postbackData(e), sourceUserID(e), parseLineOaID(raw) |
The destination queue name comes from deps.Config.RabbitMQ.QueueBookingNotification, read
from RABBITMQ_QUEUE_BOOKING_NOTIFICATION and defaulting to booking_notification.
Connections to Other Services
- RabbitMQ — the
booking_notificationqueue on theline_exchangeexchange. - worker-go — the consumer; it updates the booking status in the database and sends the cancellation confirmation back to the LINE user and to the shop owner.
- Database domain — this relates to the
appointmentdomain that stores bookings, but webhook-go never touches the database itself; it only passes thebookingIdalong to the worker. - cms-api — the
apps-appointmentmodule creates the bookings and determines that the cancel button embeds postback data in the formappt_cancel:followed by the id. - Redis —
webhook_configmust supplylineOaIdand havemboxEnabledset to"1"(see Redis Cache for Webhook Config). - This code shares a block with Handoff to Human Agents; the
mbox_team:postback is checked first.