diff --git a/CHANGELOG.md b/CHANGELOG.md index 7643d06f..e5fd08d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [Unreleased] + +### New Features +- Relay: added `call.ring()` — send 180 Ringing on an inbound call without answering it, satisfying an upstream carrier's post-dial-delay timer. The call stays unanswered and unbilled. + ## [3.2.0] - 2026-07-14 ### New Features diff --git a/relay/docs/call-methods.md b/relay/docs/call-methods.md index 8bc7e5e8..04ac9259 100644 --- a/relay/docs/call-methods.md +++ b/relay/docs/call-methods.md @@ -83,6 +83,15 @@ Answer an inbound call. await call.answer() ``` +### `ring(**kwargs) -> dict` + +Send **180 Ringing** on an inbound call without answering it, so an upstream carrier's post-dial-delay timer is satisfied. Signalling only: no media, no answer, no billing. Ringing a call takes control of it, so it can no longer be passed to another consumer. + + +```python +await call.ring() +``` + ### `hangup(reason="hangup") -> dict` End the call. diff --git a/signalwire/signalwire/relay/call.py b/signalwire/signalwire/relay/call.py index 579fcd3c..00d4788d 100644 --- a/signalwire/signalwire/relay/call.py +++ b/signalwire/signalwire/relay/call.py @@ -539,6 +539,10 @@ async def answer(self, **kwargs: Any) -> dict[str, Any]: """Answer an inbound call.""" return await self._execute("answer", kwargs or None) + async def ring(self, **kwargs: Any) -> dict[str, Any]: + """Send 180 Ringing on an inbound call without answering it.""" + return await self._execute("ring", kwargs or None) + async def hangup(self, reason: str = "hangup") -> dict[str, Any]: """End/hang up the call.""" return await self._execute("end", {"reason": reason}) diff --git a/tests/unit/relay/test_call.py b/tests/unit/relay/test_call.py index 4f2633ba..dee3898d 100644 --- a/tests/unit/relay/test_call.py +++ b/tests/unit/relay/test_call.py @@ -139,6 +139,13 @@ async def test_answer(self, call: Call, mock_client: MagicMock) -> None: "calling.answer", {"node_id": "node-1", "call_id": "call-1"} ) + @pytest.mark.asyncio + async def test_ring(self, call: Call, mock_client: MagicMock) -> None: + await call.ring() + mock_client.execute.assert_called_once_with( + "calling.ring", {"node_id": "node-1", "call_id": "call-1"} + ) + @pytest.mark.asyncio async def test_hangup(self, call: Call, mock_client: MagicMock) -> None: await call.hangup(reason="busy")