Dialpad
Dialpad is a cloud phone and meetings platform. This driver connects GEM to the Dialpad API so that a conference room can turn itself on before a meeting, follow the room's live call state, and shut down when the room is done — with nobody pressing a button.
It does two things:
- Meeting schedule → pre-warm. The driver fetches the upcoming Dialpad meetings for the users you choose and turns them into zone state on a local clock:
prewarma configurable lead time before a meeting,meetingduring it,ending_soonnear the end,idleafter. - Live call events → call state. The driver opens an outbound websocket to Dialpad's event service (no inbound webhook, no port forwarding) and follows calls on the room's Dialpad user or room phone:
ringing,in_call,call_state, the other party, and the call id. It can also hang up a call and, when enabled, place one.
One GEM device represents one Dialpad company. Each zone is one conference room.
Prerequisites
- A Dialpad company API key, created by a Dialpad company admin on the API keys page of the Dialpad Admin portal. The websocket and call-event subscription endpoints need a company key; a user-level key can read meetings but cannot stream call events.
- For pre-warm: the Dialpad user or users whose meetings book the room (see How meetings map to a room).
- Outbound HTTPS/WSS (port 443) from the controller to
dialpad.comand Dialpad's websocket host.
Setup
-
Create the API key in Dialpad Admin. Copy it; it is shown once.
-
Add the device. In Admin → System → Devices, create a device with the driver Dialpad and paste the key into Dialpad API Key. It is stored encrypted.
-
Find ids. From the device's command panel, run
get_users(optionally with anemail) andget_rooms. Each result includes a ready-made zoneaddress. -
Create one zone per conference room, with the Dialpad device as its device and one of these addresses:
Address Meaning Meetings Calls user:<id>oruser:<email>a Dialpad user (for example a dedicated room account) yes — that user's meetings yes room:<id>a Dialpad room phone via meeting_usersyes office:<id>,department:<id>, …other Dialpad call targets via meeting_usersyes A bare id or email is read as a user.
-
(Room phones and shared rooms) Set the zone attribute
meeting_usersto the comma-separated emails or ids whose meetings book the room. -
Save, then run
get_status. You should seeevents.connected: true, a subscription for each zone target, and a meeting count per watched user.
How meetings map to a room
Dialpad meetings have no room or location field. The driver only knows that a meeting belongs to a user. A room is considered booked when one of its watched users has a meeting:
- For a
user:zone, the watched user is the zone's own user, unlessmeeting_usersis set. - For any other zone, set
meeting_users.
Two setups work well:
- A dedicated room account in Dialpad (for example
boardroom@example.com). People invite it to the meetings held in the room. Useuser:boardroom@example.comas the zone address. - A short list of organizers who always meet in the room, listed in
meeting_users.
If anyone can book the room from their own account, a calendar room resource (Google Workspace / Microsoft 365) is the better schedule source. Keep this driver for live call state.
Zone state
Zones are written only when a value changes. Build triggers and macros on these:
| Attribute | Type | Meaning |
|---|---|---|
state | string | idle, prewarm, meeting or in_call. A live call wins over the schedule. Written last, so a trigger on state sees the other attributes already updated. |
meeting_state | string | The schedule alone: idle, prewarm or meeting. |
prewarm | bool | True from the pre-warm lead before a meeting until it ends. Stays true across back-to-back meetings, so it rises once per block of bookings. |
meeting_active | bool | A meeting's scheduled window contains now. |
ending_soon | bool | The current meeting ends within ending_soon_minutes, and no meeting follows it within the pre-warm lead. |
current_meeting / next_meeting | json | {id, title, start, end, organizer, meeting_url, dial_in_number, participant_pin}, or {}. |
next_meeting_title, next_meeting_start | string | Convenience copies for tiles and labels (start is ISO-8601 UTC). |
meeting_url, dial_in_number | string | Join details of the current meeting, or the next one if nothing is on now. |
in_call | bool | A call on the zone's target is connected, merged or on hold. |
ringing | bool | An inbound call is ringing. |
call_state | string | Most significant active call state (connected, merged, hold, calling, ringing, parked) or idle. |
call_id, call_direction, call_party | string | The call behind call_state; call_party is the contact's name or number. |
Device attributes maintained by the driver: events_connected (bool), last_event_at (ISO time of the last event frame) and dialpad_websocket_id (the Dialpad endpoint this device owns — leave it alone).
Example automations
- Pre-warm: trigger on zone
prewarmbecomingtrue→ displays on, DSP preset, cameras to home, lights to the conference scene, HVAC occupied. - Shutdown: trigger on zone
statebecomingidle→ wait 10 minutes → if the room's occupancy sensor is clear, run the room-off macro. - Incoming call:
ringing→ pulse the room lights or chime the panel. - On a call:
in_call→ video-conference lighting scene, "In Session" sign on. - Wrap-up:
ending_soon→ show a "5 minutes left" banner on the touch panel.
Device attributes
| Attribute | Default | Description |
|---|---|---|
api_key | — | Company API key. Secure. |
prewarm_minutes | 15 | Pre-warm lead in minutes. A zone attribute with the same name overrides it per room. |
ending_soon_minutes | 5 | How long before a meeting's end ending_soon turns on. |
meeting_poll_interval | 120000 | Milliseconds between meeting fetches (minimum 30000). State changes run on a 15-second local clock, so this only bounds how fast a new or moved booking is seen. |
lookahead_hours | 24 | How far ahead meetings are fetched. |
enable_events | true | Stream call events. Off disables all call attributes. |
max_call_hours | 8 | A call with no event for this long is dropped: the backstop for a lost hangup. |
allow_outbound_calls | false | Enables the dial command, which places real calls billed to the company. |
api_base | https://dialpad.com | Use https://sandbox.dialpad.com for a sandbox company. |
Commands
| Command | Arguments | Description |
|---|---|---|
refresh | — | Re-resolve emails, re-poll meetings and re-sync subscriptions now. |
get_status | — | Diagnostics: API health, event stream (age, last event, rotations, reconnects, rejected frames), subscriptions, meeting poll per user, per-zone state. |
get_meetings | address, hours | Fetch meetings live (not from the cache) for a zone's watched users, or for every watched user. |
get_users | email | Find users and their zone addresses. |
get_rooms | — | List Dialpad room phones and their zone addresses. |
get_meeting_rooms | — | List Dialpad Meetings rooms (dial-in numbers and join paths). |
get_subscriptions | — | List the call-event subscriptions on this device's websocket. |
get_call | call_id | Read one call. Dialpad limits this to 10 per minute. |
hangup | address or call_id | Hang up every active call on a zone, or one call. A non-admin caller may only hang up a call GEM is tracking on a zone they can control. |
dial | address, phone_number | Rings the zone user's Dialpad apps and connects them to an E.164 number. The zone must be a user: zone, and the device needs allow_outbound_calls. |
clear_call_state | address | Forget the calls tracked for a zone. |
reconnect_events | — | Close and reopen the event stream. |
remove_event_endpoint | — | Decommission: delete this device's subscriptions and websocket endpoint in Dialpad. Use it before deleting the device. |
How the event stream works
- On connect the driver creates one websocket endpoint in Dialpad, protected by a random signing secret, and stores its id on the device. Later restarts reuse the same endpoint. If someone deletes it in Dialpad, the driver creates a new one.
- The driver keeps exactly one call-event subscription per zone target on that endpoint. It adds any that are missing, removes stale or incomplete ones, and never touches subscriptions that belong to other integrations. It re-checks every 15 minutes and on
refresh. - Dialpad's connection URL is valid for one hour. Every 55 minutes the driver opens a fresh connection and closes the old one only after the new one is up, so no events are missed. Events that arrive on both connections are counted once.
- Every event is a JWT signed with the endpoint secret. The driver checks the signature, discards anything that fails, and counts it under
rejectedinget_status. - A dropped connection reconnects with backoff (1 s up to 60 s). If the gap was longer than a few seconds, the driver asks Dialpad whether the calls it still holds open have ended.
Limits and quirks
- Meeting times without a zone designator are read as UTC.
- Meetings are taken as the API returns them, one row per occurrence. Check recurring series against the live account before depending on them.
- Dialpad allows about 20 API requests per second per company. The driver spaces its own requests under that and honours
Retry-Afteron a 429. dialuses Dialpad's ring-all-devices call API (5 per minute per user). It rings the user's Dialpad devices and connects the call once one of them answers.
Troubleshooting
| Symptom | Check |
|---|---|
connected stays false; log shows dialpad 401 | The key was revoked or mistyped. Create a new company key and paste it again. |
Meetings work, but in_call never changes | Run get_status: events.connected should be true and subscriptions should list the zone's target. A user-level key cannot create websockets. Use a company key. |
Log shows zone address not understood | Use user:<id or email> or room:<id>; get_users and get_rooms print valid addresses. |
prewarm never turns on | Run get_meetings with the zone's address. If the list is empty, the meetings are under another user; add that user to meeting_users. |
events.rejected keeps rising | Frames are failing signature checks. Run reconnect_events; if it persists, remove_event_endpoint followed by refresh creates a new endpoint and secret. |