Migrating bots from v1.X to v2.0¶
Version 2.0 uses the Backend API 20 event model and supports Python 3.11, 3.12 and 3.13. In this guide, v1.X means either of the supported versions v1.7 and v1.8. They expose the same documented public API, so the migration steps are identical. Bot code must be reviewed before installing v2.0.
1. Update Python and install the SDK¶
Versions 1.7 and 1.8 provided packages for Python 3.9, 3.10 and 3.11. Version 2.0 provides packages only for Python 3.11, 3.12 and 3.13.
If the bot runs on Python 3.9 or 3.10, upgrade Python first. Create a fresh virtual environment and install the matching v2.0 package from the SDK download.
2. Update chat membership event handlers¶
The event registration API is unchanged: continue to use
Connection.set_handler() and Connection.start_notifications(). Update the
registered event constants and their meaning as follows:
| v1.X event | v2.0 migration |
|---|---|
CHAT_CREATED |
The event name and ChatChangeInfo payload class are unchanged. Review the payload compatibility notes below. |
CHAT_DELETED |
The event name is unchanged and the SDK continues to emit one callback per affected chat. In v2.0, users contains the bot user that was removed; circles and roles are empty. |
CHAT_JOINED |
Use CHAT_MEMBERS_ADDED for users, circles or roles added to a chat. In v2.0, CHAT_JOINED means that the bot itself was added to the chat. |
CHAT_LEFT |
Replace with CHAT_MEMBERS_REMOVED for users, circles or roles removed from a chat. |
CHAT_ALLOW_MUTE_NOTIFICATIONS_CHANGED |
Remove the handler; the setting and event are no longer part of the supported v2.0 public API. |
CHAT_MEMBERS_ADDED and CHAT_MEMBERS_REMOVED use
ChatMemberChangeInfo.
Its users, circles and roles lists identify the affected direct members;
lists that do not apply to an event are empty.
Circle membership has separate events:
CIRCLE_USERS_ADDEDandCIRCLE_USERS_REMOVEDreport users added to or removed from a circle that belongs to a chat. Their payload isCircleMemberUpdateInfo.CIRCLES_DELETEDreports circles deleted and removed from a chat.
This is different from adding or removing the circle itself, which produces
CHAT_MEMBERS_ADDED or CHAT_MEMBERS_REMOVED. The SDK keeps its local chat
membership state updated as these events arrive.
Role membership has the same distinction:
- Adding or removing a role as a direct chat member produces
CHAT_MEMBERS_ADDEDorCHAT_MEMBERS_REMOVED. ROLES_JOINEDandROLES_LEFTreport a user joining or leaving roles that belong to a chat.ROLE_USERS_REMOVEDreports users removed from a role, andROLES_DELETEDreports roles deleted and removed from a chat.
These role events also use ChatMemberChangeInfo; its roles and users
lists identify the affected objects.
For example, a comprehensive replacement for a v1.X subscription to
CHAT_JOINED and CHAT_LEFT is:
MEMBERSHIP_EVENTS = [
Events.CHAT_MEMBERS_ADDED,
Events.CHAT_MEMBERS_REMOVED,
Events.CIRCLE_USERS_ADDED,
Events.CIRCLE_USERS_REMOVED,
Events.CIRCLES_DELETED,
Events.ROLES_JOINED,
Events.ROLES_LEFT,
Events.ROLE_USERS_REMOVED,
Events.ROLES_DELETED,
]
connection.set_handler(MEMBERSHIP_EVENTS, membership_handler)
See the v2.0 events reference for the payload of each event.
3. Replace lists and review circle usage¶
The SDK no longer exposes user-created lists because bots cannot own lists.
In v2.0, the existing public names Group, connection.groups and gid
refer only to circles; gid is the circle's legacy ID.
Remove code that uses:
GroupType,Group.type,Group.is_list,Group.is_managed_grouporGroup.is_user_groupconnection.groups.create_group()Group.add_members()orGroup.remove_members()
There is no bot-side replacement for creating a list or changing its members. Depending on the bot's purpose, use direct users, administrator-managed public circles, or roles as chat members instead.
connection.groups.get_list() can return both public and private circles, but
bots can only add or remove public circles as chat members. Check
Group.is_public before passing a circle through the groups argument of a
chat method.
If a bot used undocumented SDK internals, also remove uses of Group.groups,
Group.get_groups() and Chat.chat_group. API 20 stores direct users, circles
and roles on the chat instead of exposing the former internal chat group.
4. Update chat creation and settings¶
Chats.create_chat() now accepts roles before message, and removes
is_alert and allow_mute_notifications. Calls that pass arguments after
groups positionally can therefore change meaning. Convert them to keyword
arguments:
chat = await connection.chats.create_chat(
title='Operations',
users=users,
groups=public_circles,
message='Welcome',
admins=admins,
allow_sending=True,
)
Also make these changes where applicable:
- Remove uses of
Chat.is_alertand theis_alertargument tocreate_chat(). Alert messages remain supported throughChat.post(is_alert=True, alert_type=...). - Remove uses of
Chat.allow_mute_notifications,Chat.set_allow_mute_notifications()and the correspondingcreate_chat()argument. There is no replacement bot-controlled setting. - Existing
Chat.add_members()andChat.remove_members()calls using direct users or public circles still work. Both methods now also acceptroles=[]. - Do not remove the bot by passing
connection.own_usertoChat.remove_members(). This now raisesTWIllegalArgument; useChat.leave()instead. Chat.leave()can raiseTWAuthErrorwhen the bot belongs to the chat only indirectly through a circle or role. Resolve that indirect membership by changing the contributing circle or role administratively, or, where permitted, by removing that circle or role from the chat.- Member updates now refresh and replace the direct membership atomically. If
an update can race with another bot, user or administrator, handle the new
TWConflictErrorand reconcile against the latest membership before retrying. An update can also raiseTWNotFoundErrorif the chat disappears while it is being refreshed.
5. Adopt roles only if needed¶
Bots that do not need role-aware behavior can ignore role-specific objects and
events. To use roles, check Permissions.roles_enabled, access them through
connection.roles, and pass Role objects through roles= when creating or
updating chats.
Use await connection.roles.get_list() or
await connection.roles.get_role(role_id) to retrieve roles.
Chat.get_role_members() returns the roles that are direct members of a chat;
use await Role.get_members() to resolve the visible users in a role.
Chat.get_user_members() continues to return only direct user members and does
not include users inherited through circles or roles.
Incoming Message
objects now have sender_roles, a list of role names frozen when the message
was sent. These are strings, not Role objects, and they do not represent the
sender's current role membership.
Permissions.roles_settings contains optional backend role configuration when
roles are enabled. Do not assume it is present. See the
roles API reference and
Working with roles for examples.
6. Review notification payload access¶
The documented event handler signature is unchanged: event handlers still
receive the event constant and, where applicable, an information object. In
v2.0, notification information objects are dataclasses with declared
attributes. Attributes that do not apply to a particular event can therefore
be present with a value of None or an empty list. Use the event constant to
distinguish events instead of testing only with hasattr().
Remove uses of deprecated v1.X payload compatibility attributes as follows:
- Replace
extra.thread_idwithextra.chat.tid. - Replace
extra.user_idswith user IDs derived fromextra.users. - Replace
extra.editor_idwithextra.editor.uid, allowing foreditorto beNone. - For
MESSAGE_READ, replace the deprecated dictionary-styleextra.get(...)orextra[...]access with the documentedchat,user,message_idandread_dateattributes.
7. Review custom asyncio event-loop setup¶
The Connection constructor now defaults to loop=None. When no loop is
provided, v2.0 creates a new asyncio event loop and installs it as the current
loop. Bots that manage their own loop, or create tasks on a loop before creating
the connection, should pass that loop explicitly through Connection(loop=...).
8. Keep handler code, but review notification network configuration¶
The SDK now receives notifications over WebSockets instead of Crossbar. This
is an internal change: Connection.set_handler(), start_notifications() and
connection event handlers keep their public interfaces, so ordinary bot handler
code does not need transport-specific changes.
Deployments with custom proxies or firewall rules must allow the API 20
WebSocket endpoint derived from TW_API_HOST at /ws/v20/. The legacy
TW_API_WS_URL override is not used by v2.0; update any deployment that
relied on it.