Poll events
Observe poll create, options, votes and clears. /v1 cannot cast a vote.
A poll's lifecycle arrives as event: poll, discriminated on eventType.
pollExternalId is the origin of the poll, not the vote row. There is no
per-row externalId on the frame.
POST /v1/commands/poll.vote is not a /v1 command. Observe peer votes
here. Casting a vote from /v1 is a separate ticket.
A created poll balloon is also a message, so you may see event: message
(often empty or non-text) and event: poll. Votes may project a junk
message too. event: poll is authoritative for lifecycle and tally.
event: poll
data: {"conversationId":"a0000000-0000-4000-8000-000000000002","occurredAt":"2026-08-24T18:04:00.000Z","from":"+14155550100","eventType":"created","pollExternalId":"poll-guid-1","question":"Which slot?","options":[{"optionExternalId":"poll-option:1","text":"10:00"}]}The union
Every variant carries conversationId, occurredAt, from, eventType and
pollExternalId. The rest depends on eventType.
Prop
Type
eventType | Extra fields | Omits |
|---|---|---|
created | question, options | vote fields |
options_changed | options | question |
vote_state | selectedOptionExternalIds | question, options |
vote_cleared | — | question, options, votes |
{
"conversationId": "a0000000-0000-4000-8000-000000000002",
"occurredAt": "2026-08-24T18:05:00.000Z",
"from": "+14155550111",
"eventType": "vote_state",
"pollExternalId": "poll-guid-1",
"selectedOptionExternalIds": ["poll-option:1"]
}Creating a poll vs watching one
You still create a poll with message.send and a
poll payload. That send settles as event: command. The balloon itself
arrives as event: poll with eventType: "created" — and often a sibling
message.
There is still no /v1 command that votes, closes, edits or deletes a poll.
Catch-up
includeActivity: true on POST /v1/replay returns the
newest limit polls independently of the newest limit reactions. The two
caps are not shared. sinceExternalId does not bound them.
activityTruncated is true if either array hit limit.