taking longer than usual.

skip to content
sometimes/ developers

developers / bots / protocol

events and reconnection

the gateway delivers authorized changes without polling. subscriptions reduce traffic; they never add permissions.

API 1 · REST + gatewaydownload reference

connection lifecycle

POST /gateway/tickets with the application token returns {url,ticket,expires_at}; tickets are single-use and expire in 60 seconds. connect to wss://sometimeschat.com/api/bots/v1/gateway with no credential in the URL. send identify within five seconds with ticket, subscriptions[] and optional resume_cursor. the server returns ready with session_id, cursor and heartbeat_interval_ms:30000. only one active gateway consumer is permitted per application in v1; a new consumer closes the old with 4009. distribute work inside your own service.

frames and acknowledgements

server frames are ready, heartbeat, event, resumed and resync_required. client frames are identify, heartbeat_ack and ack. respond to heartbeat’s nonce within 10 seconds. acknowledge the highest contiguous cursor only after durably handling every earlier event. the cursor is opaque and ordered only within this application stream. never compare its string contents. transport ACKs do not replace command acknowledgements. invalid frames close with 4002, missing identify with 4001, denied authentication with 4003 and missed heartbeat with 4008.

JSON
{
  "op": "event",
  "id": "evt_example",
  "cursor": "opaque_cursor",
  "type": "message.created",
  "installation_id": "install_example",
  "grant_revision": "2",
  "occurred_at": "2026-10-20T07:00:00Z",
  "resource_revision": "1",
  "origin": {
    "kind": "user",
    "application_id": null
  },
  "data": {
    "chat_id": "chat_example",
    "message": {
      "id": "msg_example",
      "content": "hello",
      "chat_id": "chat_example",
      "sequence": "1",
      "revision": "1",
      "kind": "text",
      "author": {
        "kind": "user",
        "id": "user_example",
        "display_name": "Avery",
        "avatar_url": null
      },
      "via_bot": null,
      "edited_via_bot": null,
      "created_at": "2026-10-20T07:00:00Z",
      "edited_at": null,
      "reply_to": null,
      "attachment_ids": null,
      "audience": "chat",
      "expires_at": null
    }
  }
}

delivery and replay

durable changes use an outbox and at-least-once delivery. duplicate event IDs are expected. order is guaranteed per installation/resource, not across chats or applications. retain up to 10,000 events per installation for at most 24 hours, whichever is reached first. replay always rechecks current permissions, audience, scope and blocks; deleted content is never resurrected from an event payload. lost access emits only a safe invalidation without old content. bot self-events include origin for loop prevention.

reconnect and recover

reconnect with a fresh ticket and the last durably acknowledged cursor, using exponential backoff with jitter from one second to 60 seconds. a valid cursor yields resumed then filtered replay. an expired cursor yields resync_required with scope:application, a reason and a new cursor; take authorized REST snapshots, reconcile resource revisions, then reconnect using that cursor so changes during snapshotting are replayed. keep a durable event-ID deduplication store for 24 hours. slow consumers receive resync_required then close 4010; maximum unacknowledged frames is 256.

event catalogue

lifecycle events are always available to the owning application with minimal metadata. content events require the listed read grants, including history for pre-boundary content. basic own-resource mutation receipts do not turn into an unrestricted read stream. fields in parentheses are the payload shape; shared object definitions are in the relevant resource chapter.

scroll the table horizontally to see all columns.

eventauthorizationdata
installation.created / updated / revokedown application; safe lifecycle metadataInstallation, or {id,reason,grant_revision} on revoke
application.updated / disabledown application{id,published_revision,status}
chat.access_changedaffected installation only{chat_id,accessible,reason}; no hidden title/content
message.created / updatedapplicable author read + history if needed{chat_id,message:Message}
message.deletedcurrently eligible author/category; previously visible ID{chat_id,message_id,revision}
scribble.created / updated / erasedscribbles.read{chat_id,stroke:Scribble} or {chat_id,ids,cuts,revision}
scribble.previewscribbles.read; transient{chat_id,actor,stroke,expires_at}; no replay
placement.created / updated / removedscribbles.read or readable parent message{chat_id,placement:Placement} or {chat_id,id,revision}
reaction.updated / pin.updatedreadable target message{chat_id,message_id,reactions} or {chat_id,message_id,pins}
poll.updated / closedpolls.results + readable poll{chat_id,poll:Poll}; anonymous voters omitted
presence.updatedpresence.read; eligible user; transient{user_id,online,expires_at}; no replay
typing.started / stoppedtyping.read; authorized chat; transient{chat_id,user_id,expires_at}; no draft text
group.updated / members.updatedauthorized group; roster only with groups.members.read{chat_id,revision,group?} or {chat_id,members}
friends.updated / friend_request.updatedfriends.read / friends.requests.read{revision,refresh:true} or {request_id,revision,request}
profile.updatedmatching profile read grant{user_id,profile:Profile,revision}
settings.updated / stickers.updatedsettings.write / stickers.manage{target,patch,revision} / {sticker_id,operation,revision}
call.created / updated / endedstart or join grant; in-scope call{chat_id,call:Call}; no raw SFU credentials
call.track.added / removedjoined session + matching media receive grant{session_id,track:Track} or {session_id,track_id}
call.invitation.updatedbot.calls.invite; own proposal{invitation_id,status,expires_at}
interaction.createdcommands for private command invocations; bot.messages.components for component actions with the actual member actor_idInteraction; explicit arguments only
game.started / endedbot.games.start; own-started game{chat_id,game_session_id,status}; no player game state

revocation during delivery

uninstall and invite removal atomically revoke grants and enqueue a minimal lifecycle notification. dispatch, replay, upload completion and media authorization must consult current revocation state. purge pending content and tear down streams; do not rely on eventual cache expiry for enforcement. if a resource became invisible after a client saw it, send only an invalidation for its known ID. downloaded data cannot be recalled; developers must honor deletion and retention commitments. webhooks are not a second event transport in v1, avoiding two delivery contracts.