diff --git a/doap.xml b/doap.xml
index 20ad179d..6774a786 100644
--- a/doap.xml
+++ b/doap.xml
@@ -941,6 +941,14 @@
1.8.6
+
+
+
+ complete
+ 0.1.0
+ 1.8.7
+
+
diff --git a/slixmpp/plugins/__init__.py b/slixmpp/plugins/__init__.py
index 589cef46..d57be35c 100644
--- a/slixmpp/plugins/__init__.py
+++ b/slixmpp/plugins/__init__.py
@@ -122,6 +122,7 @@ PLUGINS = [
'xep_0447', # Stateless file sharing
'xep_0461', # Message Replies
'xep_0469', # Bookmarks Pinning
+ 'xep_0482', # Call Invites
'xep_0490', # Message Displayed Synchronization
'xep_0492', # Chat Notification Settings
# Meant to be imported by plugins
diff --git a/slixmpp/plugins/xep_0482/__init__.py b/slixmpp/plugins/xep_0482/__init__.py
new file mode 100644
index 00000000..66105db0
--- /dev/null
+++ b/slixmpp/plugins/xep_0482/__init__.py
@@ -0,0 +1,11 @@
+# Slixmpp: The Slick XMPP Library
+# Copyright (C) 2025 Mathieu Pasquet
+# This file is part of Slixmpp.
+# See the file LICENSE for copying permissio
+from slixmpp.plugins.base import register_plugin
+
+from slixmpp.plugins.xep_0482 import stanza
+from slixmpp.plugins.xep_0482.call_invites import XEP_0482
+
+
+register_plugin(XEP_0482)
diff --git a/slixmpp/plugins/xep_0482/call_invites.py b/slixmpp/plugins/xep_0482/call_invites.py
new file mode 100644
index 00000000..a118f3eb
--- /dev/null
+++ b/slixmpp/plugins/xep_0482/call_invites.py
@@ -0,0 +1,55 @@
+# Slixmpp: The Slick XMPP Library
+# Copyright (C) 2025 Mathieu Pasquet
+# This file is part of Slixmpp.
+# See the file LICENSE for copying permissio
+import logging
+from typing import Optional
+
+from slixmpp.stanza import Message
+from slixmpp.jid import JID
+from slixmpp.xmlstream.handler import Callback
+from slixmpp.xmlstream.matcher import StanzaPath
+from slixmpp.xmlstream import register_stanza_plugin
+from slixmpp.plugins import BasePlugin
+from slixmpp.plugins.xep_0482 import stanza
+
+
+log = logging.getLogger(__name__)
+
+
+class XEP_0482(BasePlugin):
+
+ """
+ XEP-0482: Call Invites
+
+ This plugin defines the stanza elements for Call Invites, as well as new
+ events:
+
+ - `call-invite`
+ - `call-reject`
+ - `call-retract`
+ - `call-leave`
+ - `call-left`
+ """
+
+ name = 'xep_0482'
+ description = 'XEP-0482: Call Invites'
+ dependencies = set()
+ stanza = stanza
+
+ def plugin_init(self):
+ stanza.register_plugins()
+
+ for event in ('invite', 'reject', 'retract', 'leave', 'left'):
+ self.xmpp.register_handler(
+ Callback(f'Call {event}',
+ StanzaPath(f'message/call-{event}'),
+ self._handle_event))
+ def _handle_event(self, message):
+ for event in ('invite', 'reject', 'retract', 'leave', 'left'):
+ if message.get_plugin(f'call-{event}', check=True):
+ self.xmpp.event(f'call-{event}')
+
+ def plugin_end(self):
+ for event in ('invite', 'reject', 'retract', 'leave', 'left'):
+ self.xmpp.remove_handler(f'Call {event}')
diff --git a/slixmpp/plugins/xep_0482/stanza.py b/slixmpp/plugins/xep_0482/stanza.py
new file mode 100644
index 00000000..3ca3c630
--- /dev/null
+++ b/slixmpp/plugins/xep_0482/stanza.py
@@ -0,0 +1,102 @@
+# Slixmpp: The Slick XMPP Library
+# Copyright (C) 2025 Mathieu Pasquet
+# This file is part of Slixmpp.
+# See the file LICENSE for copying permission
+
+from typing import Tuple, List, Optional
+from slixmpp import Message
+from slixmpp.jid import JID
+from slixmpp.xmlstream import ElementBase, register_stanza_plugin
+
+NS = 'urn:xmpp:call-invites:0'
+
+
+class Jingle(ElementBase):
+ name = 'jingle'
+ namespace = NS
+ plugin_attrib = 'jingle'
+ plugin_multi_attrib = 'jingles'
+ interfaces = {'sid', 'jid'}
+
+ def set_jid(self, value: JID) -> None:
+ if not isinstance(value, JID):
+ try:
+ value = JID(value)
+ except ValueError:
+ raise ValueError(f'"jid" must be a valid JID object')
+ self.xml.attrib['jid'] = value.full
+
+ def get_jid(self) -> Optional[JID]:
+ try:
+ return JID(self.xml.attrib.get('jid', ''))
+ except ValueError:
+ return None
+
+
+class External(ElementBase):
+ name = 'external'
+ namespace = NS
+ plugin_attrib = 'external'
+ plugin_multi_attrib = 'externals'
+ interfaces = {'uri'}
+
+
+class Invite(ElementBase):
+ name = 'invite'
+ namespace = NS
+ plugin_attrib = 'call-invite'
+ interfaces = {'video'}
+
+ def get_methods(self) -> Tuple[List[Jingle], List[External]]:
+ return (self['jingles'], self['externals'])
+
+ def set_video(self, value: bool) -> None:
+ if not isinstance(value, bool):
+ raise ValueError(f'Invalid value for the video attribute: {value}')
+ self.xml.attrib['video'] = str(value).lower()
+
+ def get_video(self) -> bool:
+ vid = self.xml.attrib.get('video', 'false').lower()
+ return vid == 'true'
+
+
+class Retract(ElementBase):
+ name = 'retract'
+ namespace = NS
+ plugin_attrib = 'call-retract'
+ interfaces = {'id'}
+
+
+class Accept(ElementBase):
+ name = 'accept'
+ namespace = NS
+ plugin_attrib = 'call-accept'
+ interfaces = {'id'}
+
+
+class Reject(ElementBase):
+ name = 'reject'
+ namespace = NS
+ plugin_attrib = 'call-reject'
+ interfaces = {'id'}
+
+
+class Left(ElementBase):
+ name = 'left'
+ namespace = NS
+ plugin_attrib = 'call-left'
+ interfaces = {'id'}
+
+
+def register_plugins() -> None:
+ register_stanza_plugin(Message, Invite)
+ register_stanza_plugin(Message, Retract)
+ register_stanza_plugin(Message, Accept)
+ register_stanza_plugin(Message, Reject)
+ register_stanza_plugin(Message, Left)
+
+ register_stanza_plugin(Invite, Jingle, iterable=True)
+ register_stanza_plugin(Invite, External, iterable=True)
+
+ register_stanza_plugin(Accept, Jingle)
+ register_stanza_plugin(Accept, External)
diff --git a/tests/test_stanza_xep_0482.py b/tests/test_stanza_xep_0482.py
new file mode 100644
index 00000000..a3572793
--- /dev/null
+++ b/tests/test_stanza_xep_0482.py
@@ -0,0 +1,42 @@
+import unittest
+from slixmpp import Message
+from slixmpp.jid import JID
+from slixmpp.test import SlixTest
+from slixmpp.plugins.xep_0482 import stanza
+from slixmpp.plugins.xep_0482.stanza import External, Jingle
+from slixmpp.xmlstream import register_stanza_plugin
+
+
+class TestCallInviteStanza(SlixTest):
+
+ def setUp(self):
+ stanza.register_plugins()
+
+ def test_invite(self):
+ """Test that the element is created correctly."""
+ msg = Message()
+ msg['call-invite']['video'] = True
+ jingle = Jingle()
+ jingle['sid'] = 'toto'
+ jingle['jid'] = JID('toto@example.com/m')
+ external = External()
+ external['uri'] = "https://example.com/call"
+ msg['call-invite'].append(jingle)
+ msg['call-invite'].append(external)
+
+ self.check(msg, """
+
+
+
+
+
+
+ """)
+
+ self.assertEqual(
+ msg['call-invite'].get_methods(),
+ ([jingle], [external]),
+ )
+
+
+suite = unittest.TestLoader().loadTestsFromTestCase(TestCallInviteStanza)