Response stream

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

typeWhenCarries
poll.createdA poll was created — by a contact or the linequestion, options
poll.options_changedA choice was added to itoptions: the whole list, new ids and old
poll.votedSomeone voted, or changed their voteselectedOptionExternalIds
poll.vote_clearedSomeone 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 its poll.created carried.
  • Replace, never append. poll.options_changed carries the whole current list of choices; replace the list you hold with it.
  • A vote is the voter's whole selection. selectedOptionExternalIds is 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. A poll.vote_cleared means 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.

poll.voted — a contact picked both slots
{
  "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.

On this page