How to Build a Customer Call (Queue) Display
This guide shows you how to build a ticket-calling display: a large panel shows the customer being called now, and a table below lists the last few tickets. In a demo, a button adds new tickets. In production, your ticket kiosks add them through a webhook.
How it works
| Part | What it does |
|---|---|
| Internal datasource | Holds the queue as a list of tickets |
| Table Widget (5 rows) | Shows the newest tickets |
| Table Widget (1 row) | Shows only the newest ticket, as the featured "now calling" panel |
| Button (demo) | Adds a random ticket with an action batch |
| Webhook (production) | Lets each kiosk add a ticket from outside Wallboard |
Both tables read the same list, sorted newest first. When a ticket is added, both tables show it without any extra setup. ❓ Confirm how quickly the change reaches a device that did not make it (the Notion notes say sub-second, over WebSocket).
Step 1: Create the queue datasource
- Go to Datasources and click Add internal datasource.
- Set Structure type to Custom.
- Enter the queue as a list. Seed it with about ten tickets, so the demo has something to show from the start:
{
"customer_queue": [
{ "customer_number": "001", "kiosk_number": 1, "timestamp": "2026-04-22T14:02:15Z" },
{ "customer_number": "002", "kiosk_number": 3, "timestamp": "2026-04-22T14:03:40Z" }
]
}
- Turn on Allow displays to update data.
- Save.
The button in Step 4 runs on the display. Without Allow displays to update data, the server rejects every insert and nothing happens on the screen.
Learn more: Internal Custom Datasources
Step 2: Add the queue table
- Add a Table Widget and bind it to the queue datasource. Select the
customer_queuelist, so each ticket becomes a row. - Choose the columns:
customer_number,kiosk_numberandtimestamp. Rename the headers as you like, for example Ticket, Desk and Time. - In the data picker, open Table level settings:
- Order: set Column to
timestampand switch the order to descending with the Change order type button. - Limit: set Only display to
5.
- Order: set Column to
Learn more: Table Widget
Step 3: Add the featured "now calling" panel
- Duplicate the queue table.
- In the copy, set Only display to
1. - Make it large and style it as the featured panel.
Because the copy is sorted newest first and shows one row, it always shows the most recent ticket. There is nothing to rebind when new tickets arrive.
Step 4: Add a "Call new customer" button (demo)
For a demo, a button creates a random ticket and inserts it at the top of the queue.
- Add a button, select it, and open Edit touch events on its Other tab.
- Add the action Execute action batch:
- Actions origin: Local
- Sent fields: add one field. Set its type to Call function, its Name to
receivedData, and its Value to this function:
(receivedValue) => {
const customer_number = String(Math.floor(Math.random() * 999) + 1).padStart(3, '0');
const kiosk_number = Math.floor(Math.random() * 4) + 1;
const timestamp = new Date().toISOString().split('.')[0] + 'Z';
return { customer_number, kiosk_number, timestamp };
}
- Inside the batch, add the action Insert to internal datasource array:
| Field | Value |
|---|---|
| Datasource | Your queue datasource, with customer_queue selected |
| Index | 0 (insert at the top) |
| Max element count | Empty, or a cap such as 50 (see below) |
| Rotate enabled | On, if you set a cap |
| Value origin | Property of received value |
| Value property name | receivedData |
Max element count on its own does not trim the list. When the list is full, new tickets are silently skipped. Turn on Rotate enabled as well: with Index 0, the oldest tickets are then dropped from the end, and the list keeps the newest ones.
❓ Call function values are blocked in sessions where custom scripts are not allowed. Confirm that this does not affect a normal device.
Learn more: Sent Fields Explained
Learn more: Insert To Array
Step 5: Test
Open the Content in preview and click the button. The new ticket appears at the top of the table and in the featured panel at the same time.
Going live: tickets from the kiosks
In production, the button is replaced by the ticket kiosks themselves. Each kiosk calls a Wallboard webhook when a customer takes a ticket, and the webhook inserts the ticket into the same list.
- Go to the Integration menu, open the API Keys tab, and click Add API key.
- Open the Actions tab and click Add action. Give it an Event ID and choose the action type Insert to internal datasource array. Select the queue datasource and the
customer_queuelist, and set Index, Max element count and Rotate as in Step 4. - Use Generate example link (with Copy link) or Test to check the action, then give the kiosk vendor the webhook address, the API key and the Event ID.
- The kiosk sends the new ticket in the
datafield of the request, for example:
{
"eventId": "new-ticket",
"data": { "customer_number": "043", "kiosk_number": 2, "timestamp": "2026-04-22T14:05:02Z" }
}
The request can also send index, maxElementCount and rotateEnabled; when it does, they override the action's own settings.
❓ The webhook is authenticated by the API key, not by a display, so Allow displays to update data should not be needed for this path. Confirm before telling a customer they can turn it off.
Learn more: Datasource actions
Learn more: API keys
Serving the next customer
A second button (for example on a clerk's tablet) can take a ticket off the list with the action Remove from internal datasource array. Set Index to the position to remove. With an empty Index, the last item in the list is removed, which is the oldest ticket when new tickets are inserted at 0.
Learn more: Remove From Array
Where should the data live?
This example keeps the queue in Wallboard. That is one of two ways to connect a queue system:
| The queue lives in… | How it works | Choose it when |
|---|---|---|
| Wallboard (this guide) | The kiosk system calls Wallboard's webhook and pushes each new ticket into an internal datasource | The kiosks can send an HTTP request, and Wallboard only needs to display the queue |
| The external system | Wallboard reads the queue from the system's own API, for example as a JSON datasource, or sends changes to it | The queue system already holds the data, or other systems also need to change it |
Decide which system owns the source of truth before you build: whether the external system already has the data, and whether Wallboard only shows the queue or also changes it.
Related articles
- Learn more: Internal datasource change via webhook event
- Learn more: What Are Action Batches