Poll events
A poll in a conversation with the line — created, a choice added, a vote, a vote taken back — and the ids a vote needs. On request only.
When a poll lives in a conversation with your line — one a contact created, or
one the line sent — four event types report it: poll.created,
poll.options_changed when a choice is added, poll.voted when someone votes,
and poll.vote_cleared when someone takes their vote back. None of them
arrives unless you ask for it.
They are also the only place the ids of a poll's choices arrive. To vote with
poll.vote — on your own poll or a contact's — you need the
pollExternalId and the choice's optionExternalId from the poll's
poll.created.
Asking for them
On the stream, open it with ?events=polls. All four types arrive as one
frame, event: poll, told apart by its type:
curl -N "https://api.mapier.ai/v1/response_stream?events=polls" \
-H "Authorization: Bearer $MAPIER_API_KEY" \
-H "x-mapier-version: 2026-10-15" \
-H "Accept: text/event-stream"id: 0192a7c4-b5e3-7a41-9c28-4e1f6a0b2d93
event: poll
data: {"type":"poll.created","conversationId":"a0000000-0000-4000-8000-000000000001","actor":"contact","contactId":"c0000000-0000-4000-8000-000000000003","pollExternalId":"5E9A2B4C-6D8F-4A1B-8C3D-2E4F6A8B0C1D","question":"Which slot works for Saturday?","options":[{"optionExternalId":"D4E9A1C3-2F5B-4A8C-9D7E-0F1A3B5C7D9E","text":"10:00"},{"optionExternalId":"E5F0B2D4-3A6C-4B9D-8E1F-2A4B6C8D0E1F","text":"13:30"}],"occurredAt":"2026-10-01T17:58:02.000Z","simulated":false}The option combines with the others: ?events=receipts,polls. The frame's
fields are in the stream reference.
At a webhook endpoint, subscribe to the types by name on the project's Webhooks page. They are not in the default set: a new endpoint, and Select all, leave them out: tick them by name. See Webhooks.
The four moments
type | When | Carries |
|---|---|---|
poll.created | A poll was created — by a contact or the line | question, options |
poll.options_changed | A choice was added to it | options: the whole list, new ids and old |
poll.voted | Someone voted, or changed their vote | selectedOptionExternalIds |
poll.vote_cleared | Someone took their vote back and now holds none | — |
Every moment carries conversationId, actor, contactId, pollExternalId,
occurredAt and simulated. Three rules keep a tally right:
- Match by
pollExternalId. Every moment of one poll repeats the id itspoll.createdcarried. - Replace, never append.
poll.options_changedcarries the whole current list of choices; replace the list you hold with it. - A vote is the voter's whole selection.
selectedOptionExternalIdsis every choice that person holds now — several, on a poll where they picked several — so set that voter's selection to it rather than adding to it. Apoll.vote_clearedmeans they hold none.
Tell voters apart by actor and contactId: actor: "line" is your line,
with contactId: null; actor: "contact" is a participant, with their
contactId — or null for a member of a group your project holds no contact
for, so two such members' votes cannot be told apart. Option ids are opaque:
store them and pass them back exactly as given.
{
"type": "poll.voted",
"conversationId": "a0000000-0000-4000-8000-000000000001",
"actor": "contact",
"contactId": "c0000000-0000-4000-8000-000000000003",
"pollExternalId": "5E9A2B4C-6D8F-4A1B-8C3D-2E4F6A8B0C1D",
"selectedOptionExternalIds": [
"D4E9A1C3-2F5B-4A8C-9D7E-0F1A3B5C7D9E",
"E5F0B2D4-3A6C-4B9D-8E1F-2A4B6C8D0E1F"
],
"occurredAt": "2026-10-01T18:03:45.000Z",
"simulated": false
}Your own poll comes back here
A poll the line sends with message.send settles as
a command frame like any send, and also arrives here as a poll.created
with actor: "line" — carrying the ids its choices were given. That is how you
learn them: the command frame carries no choice ids. Do not rely on which of
the two arrives first. Each vote the line makes
with poll.vote comes back as a poll.voted with actor: "line".
The question also arrives as a message
When a contact creates a poll, Messages sends its question as ordinary text
beside it, so the question also reaches you as a message frame and a
message.received webhook from that contact — whether or not you asked for
poll events. It is the same question, not a second one. The message carries
no poll id; if you handle polls, match it to the poll.created by
conversation, sender and text, and answer it once.
The poll itself, an added choice and each vote are bubbles with no words. They
are not delivered as messages once the platform holds the poll event they
belong to, but one that reaches the platform ahead of its event can still
arrive as a message with no text, or with only the replacement character
U+FFFD. Treat such a message as nothing to answer: the poll frame is the
poll's record.
Where a poll is filed
A poll is filed on the conversation your project holds for its chat — a one-to-one conversation, or a group's. A poll in a chat the line has no conversation for is not framed.
In test
A test line rehearses the line's own side of a poll. A poll it sends comes back
as poll.created with actor: "line", and a poll.vote on it comes back as
poll.voted, each with "simulated": true. Nobody on the sandbox phone
creates polls or votes, so a contact's poll is not rehearsed. The opt-in works
as it does live: a stream without the option frames none,
and an endpoint on the default set hears none.