Skip to content

Event History ​

Every answer, reply, completion, and submission is an event on your account's stream. sp events, sp collect, and the SDK streams all read this stream. You can also read it raw when you want the events themselves rather than a handle for one send.

Reading the stream ​

sp events prints the raw feed. In the SDKs it is client.events() on both Client and OrgClient. Both stream live by default. To replay history first, pass --since on the CLI (24h, 7d, or ISO 8601). Add --follow to keep streaming once the replay has caught up.

bash
sp events --since 24h --follow

Your personal stream carries the events for everything you sent. Answers, replies, and completions go to the sender. The stream also carries submissions created on your own account. An organization API key reads the organization's stream instead.

The event envelope ​

json
{
  "streamId": "…",
  "version": 42,
  "eventType": "TaskInputCompleted",
  "createdAt": "2026-07-19T12:00:00Z",
  "actor": {
    "publicId": "usr_…",
    "name": "Alice",
    "devicePublicId": "dev_…",
    "deviceName": "Alice's iPhone"
  },
  "data": {
    "type": "taskInputCompleted",
    "taskId": "tsk_…",
    "inputUploaded": {
      "type": "choiceSelected",
      "id": "inp_…",
      "selectedIndex": 0,
      "selectedValue": "Approve"
    }
  }
}
FieldDescription
streamIdThe stream the event belongs to.
versionSequential number within the stream, used for ordering and exact resumption.
eventTypePascalCase type, see below.
actorWho acted, with display name and device. Present on receive-side events (someone answered, replied, or submitted). Absent on collective events.
dataType-specific payload, discriminated by a camelCase type.
encryption{ "type": "personal", "keyFingerprint": … } or { "type": "org", "v": … } when the payload is encrypted. Absent for plaintext.
createdAtISO 8601 timestamp.

Optional fields are omitted from the JSON when empty, never null. All entity ids are prefixed (tsk_, sub_, ntf_, sbm_, inp_, usr_, dev_, grptsk_, grpntf_). Treat them as opaque strings.

Event types ​

eventTypeEmitted when
TaskInputUploadedAn input answer arrived (auto-commit off: not yet committed).
TaskInputCompletedAn input was answered and committed.
TaskCompletedAll required inputs are done (or the task was completed manually). data.inputsUploaded carries every answer.
TaskDeletedByRecipient / TaskDeletedThe task was removed before completion.
SubtaskInputUploaded / SubtaskInputCompleted / SubtaskCompletedThe same lifecycle for an appended subtask, with subtaskId and parentTaskId.
ReplyAppendedA reply-composer reply: text, photo, file, audio, or location.
NotificationCompletedA notification input was answered (data.reply is the text, choice, or action reply).
SubmissionCreatedSomeone sent a submission from the app: any of text, photo, file, audio, location in one event.

The inputUploaded payload varies by input kind:

typeFields
textUploadedid, value
choiceSelectedid, selectedIndex, selectedValue
multiChoiceSelectedid, selectedIndices, selectedValues (parallel arrays; may be empty for an optional input)
actionSelectedid, selectedKey
sliderUploadedid, value
photoUploaded / fileUploadedid, contentType, checksumSha256, size (+ filename for files)
voiceRecordedid, contentType, checksumSha256, size, durationSeconds
locationUploadedid, latitude, longitude, accuracy, altitude, heading, speed, timestamp, or a single encrypted blob when the task is end-to-end encrypted

Waiting on a single send ​

Besides the account-wide stream, each send can be observed on its own with its wait token. Every send returns one. The token replays the events of that one send and streams new ones until the send completes. It grants no other access on the account.

Wait: true on a curl send and sp task --wait use the wait token. It also lets a process that holds only the wait token, and not your API token, safely observe one task and nothing else.