telnyx 3.9.0__py3-none-any.whl → 3.11.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of telnyx might be problematic. Click here for more details.

Files changed (56) hide show
  1. telnyx/_streaming.py +4 -6
  2. telnyx/_version.py +1 -1
  3. telnyx/resources/__init__.py +1 -2
  4. telnyx/resources/ai/__init__.py +28 -0
  5. telnyx/resources/ai/ai.py +64 -0
  6. telnyx/resources/ai/integrations/__init__.py +33 -0
  7. telnyx/resources/ai/integrations/connections.py +294 -0
  8. telnyx/resources/ai/integrations/integrations.py +246 -0
  9. telnyx/resources/ai/mcp_servers.py +571 -0
  10. telnyx/resources/calls/actions.py +8 -0
  11. telnyx/resources/calls/calls.py +8 -0
  12. telnyx/resources/conferences/actions.py +180 -8
  13. telnyx/resources/conferences/conferences.py +50 -3
  14. telnyx/resources/recordings/recordings.py +2 -2
  15. telnyx/resources/texml/accounts/calls/calls.py +8 -0
  16. telnyx/resources/webhooks.py +82 -94
  17. telnyx/types/__init__.py +1 -0
  18. telnyx/types/ai/__init__.py +9 -0
  19. telnyx/types/ai/integration_list_response.py +28 -0
  20. telnyx/types/ai/integration_retrieve_response.py +24 -0
  21. telnyx/types/ai/integrations/__init__.py +6 -0
  22. telnyx/types/ai/integrations/connection_list_response.py +19 -0
  23. telnyx/types/ai/integrations/connection_retrieve_response.py +19 -0
  24. telnyx/types/ai/mcp_server_create_params.py +22 -0
  25. telnyx/types/ai/mcp_server_create_response.py +24 -0
  26. telnyx/types/ai/mcp_server_list_params.py +19 -0
  27. telnyx/types/ai/mcp_server_list_response.py +28 -0
  28. telnyx/types/ai/mcp_server_retrieve_response.py +24 -0
  29. telnyx/types/ai/mcp_server_update_params.py +28 -0
  30. telnyx/types/ai/mcp_server_update_response.py +24 -0
  31. telnyx/types/call_dial_params.py +3 -0
  32. telnyx/types/calls/action_transfer_params.py +3 -0
  33. telnyx/types/conference_create_params.py +6 -0
  34. telnyx/types/conference_list_params.py +3 -0
  35. telnyx/types/conference_list_participants_params.py +4 -1
  36. telnyx/types/conference_retrieve_params.py +12 -0
  37. telnyx/types/conferences/action_hold_params.py +7 -1
  38. telnyx/types/conferences/action_join_params.py +6 -0
  39. telnyx/types/conferences/action_leave_params.py +6 -0
  40. telnyx/types/conferences/action_mute_params.py +7 -1
  41. telnyx/types/conferences/action_play_params.py +7 -1
  42. telnyx/types/conferences/action_record_pause_params.py +7 -1
  43. telnyx/types/conferences/action_record_resume_params.py +7 -1
  44. telnyx/types/conferences/action_record_start_params.py +6 -0
  45. telnyx/types/conferences/action_record_stop_params.py +7 -1
  46. telnyx/types/conferences/action_speak_params.py +6 -0
  47. telnyx/types/conferences/action_stop_params.py +7 -1
  48. telnyx/types/conferences/action_unhold_params.py +7 -1
  49. telnyx/types/conferences/action_unmute_params.py +7 -1
  50. telnyx/types/conferences/action_update_params.py +6 -0
  51. telnyx/types/recording_list_params.py +7 -1
  52. telnyx/types/texml/accounts/call_calls_params.py +5 -0
  53. {telnyx-3.9.0.dist-info → telnyx-3.11.0.dist-info}/METADATA +1 -1
  54. {telnyx-3.9.0.dist-info → telnyx-3.11.0.dist-info}/RECORD +56 -39
  55. {telnyx-3.9.0.dist-info → telnyx-3.11.0.dist-info}/WHEEL +0 -0
  56. {telnyx-3.9.0.dist-info → telnyx-3.11.0.dist-info}/licenses/LICENSE +0 -0
@@ -6,7 +6,12 @@ from typing_extensions import Literal
6
6
 
7
7
  import httpx
8
8
 
9
- from ...types import conference_list_params, conference_create_params, conference_list_participants_params
9
+ from ...types import (
10
+ conference_list_params,
11
+ conference_create_params,
12
+ conference_retrieve_params,
13
+ conference_list_participants_params,
14
+ )
10
15
  from .actions import (
11
16
  ActionsResource,
12
17
  AsyncActionsResource,
@@ -71,6 +76,7 @@ class ConferencesResource(SyncAPIResource):
71
76
  hold_audio_url: str | Omit = omit,
72
77
  hold_media_name: str | Omit = omit,
73
78
  max_participants: int | Omit = omit,
79
+ region: Literal["Australia", "Europe", "Middle East", "US"] | Omit = omit,
74
80
  start_conference_on_create: bool | Omit = omit,
75
81
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
76
82
  # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -129,6 +135,9 @@ class ConferencesResource(SyncAPIResource):
129
135
  max_participants: The maximum number of active conference participants to allow. Must be between 2
130
136
  and 800. Defaults to 250
131
137
 
138
+ region: Sets the region where the conference data will be hosted. Defaults to the region
139
+ defined in user's data locality settings (Europe or US).
140
+
132
141
  start_conference_on_create: Whether the conference should be started on creation. If the conference isn't
133
142
  started all participants that join are automatically put on hold. Defaults to
134
143
  "true".
@@ -155,6 +164,7 @@ class ConferencesResource(SyncAPIResource):
155
164
  "hold_audio_url": hold_audio_url,
156
165
  "hold_media_name": hold_media_name,
157
166
  "max_participants": max_participants,
167
+ "region": region,
158
168
  "start_conference_on_create": start_conference_on_create,
159
169
  },
160
170
  conference_create_params.ConferenceCreateParams,
@@ -169,6 +179,7 @@ class ConferencesResource(SyncAPIResource):
169
179
  self,
170
180
  id: str,
171
181
  *,
182
+ region: Literal["Australia", "Europe", "Middle East", "US"] | Omit = omit,
172
183
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
173
184
  # The extra values given here take precedence over values defined on the client or passed to this method.
174
185
  extra_headers: Headers | None = None,
@@ -180,6 +191,8 @@ class ConferencesResource(SyncAPIResource):
180
191
  Retrieve an existing conference
181
192
 
182
193
  Args:
194
+ region: Region where the conference data is located
195
+
183
196
  extra_headers: Send extra headers
184
197
 
185
198
  extra_query: Add additional query parameters to the request
@@ -193,7 +206,11 @@ class ConferencesResource(SyncAPIResource):
193
206
  return self._get(
194
207
  f"/conferences/{id}",
195
208
  options=make_request_options(
196
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
209
+ extra_headers=extra_headers,
210
+ extra_query=extra_query,
211
+ extra_body=extra_body,
212
+ timeout=timeout,
213
+ query=maybe_transform({"region": region}, conference_retrieve_params.ConferenceRetrieveParams),
197
214
  ),
198
215
  cast_to=ConferenceRetrieveResponse,
199
216
  )
@@ -203,6 +220,7 @@ class ConferencesResource(SyncAPIResource):
203
220
  *,
204
221
  filter: conference_list_params.Filter | Omit = omit,
205
222
  page: conference_list_params.Page | Omit = omit,
223
+ region: Literal["Australia", "Europe", "Middle East", "US"] | Omit = omit,
206
224
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
207
225
  # The extra values given here take precedence over values defined on the client or passed to this method.
208
226
  extra_headers: Headers | None = None,
@@ -228,6 +246,8 @@ class ConferencesResource(SyncAPIResource):
228
246
  page: Consolidated page parameter (deepObject style). Originally: page[after],
229
247
  page[before], page[limit], page[size], page[number]
230
248
 
249
+ region: Region where the conference data is located
250
+
231
251
  extra_headers: Send extra headers
232
252
 
233
253
  extra_query: Add additional query parameters to the request
@@ -247,6 +267,7 @@ class ConferencesResource(SyncAPIResource):
247
267
  {
248
268
  "filter": filter,
249
269
  "page": page,
270
+ "region": region,
250
271
  },
251
272
  conference_list_params.ConferenceListParams,
252
273
  ),
@@ -260,6 +281,7 @@ class ConferencesResource(SyncAPIResource):
260
281
  *,
261
282
  filter: conference_list_participants_params.Filter | Omit = omit,
262
283
  page: conference_list_participants_params.Page | Omit = omit,
284
+ region: Literal["Australia", "Europe", "Middle East", "US"] | Omit = omit,
263
285
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
264
286
  # The extra values given here take precedence over values defined on the client or passed to this method.
265
287
  extra_headers: Headers | None = None,
@@ -277,6 +299,8 @@ class ConferencesResource(SyncAPIResource):
277
299
  page: Consolidated page parameter (deepObject style). Originally: page[after],
278
300
  page[before], page[limit], page[size], page[number]
279
301
 
302
+ region: Region where the conference data is located
303
+
280
304
  extra_headers: Send extra headers
281
305
 
282
306
  extra_query: Add additional query parameters to the request
@@ -298,6 +322,7 @@ class ConferencesResource(SyncAPIResource):
298
322
  {
299
323
  "filter": filter,
300
324
  "page": page,
325
+ "region": region,
301
326
  },
302
327
  conference_list_participants_params.ConferenceListParticipantsParams,
303
328
  ),
@@ -343,6 +368,7 @@ class AsyncConferencesResource(AsyncAPIResource):
343
368
  hold_audio_url: str | Omit = omit,
344
369
  hold_media_name: str | Omit = omit,
345
370
  max_participants: int | Omit = omit,
371
+ region: Literal["Australia", "Europe", "Middle East", "US"] | Omit = omit,
346
372
  start_conference_on_create: bool | Omit = omit,
347
373
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
348
374
  # The extra values given here take precedence over values defined on the client or passed to this method.
@@ -401,6 +427,9 @@ class AsyncConferencesResource(AsyncAPIResource):
401
427
  max_participants: The maximum number of active conference participants to allow. Must be between 2
402
428
  and 800. Defaults to 250
403
429
 
430
+ region: Sets the region where the conference data will be hosted. Defaults to the region
431
+ defined in user's data locality settings (Europe or US).
432
+
404
433
  start_conference_on_create: Whether the conference should be started on creation. If the conference isn't
405
434
  started all participants that join are automatically put on hold. Defaults to
406
435
  "true".
@@ -427,6 +456,7 @@ class AsyncConferencesResource(AsyncAPIResource):
427
456
  "hold_audio_url": hold_audio_url,
428
457
  "hold_media_name": hold_media_name,
429
458
  "max_participants": max_participants,
459
+ "region": region,
430
460
  "start_conference_on_create": start_conference_on_create,
431
461
  },
432
462
  conference_create_params.ConferenceCreateParams,
@@ -441,6 +471,7 @@ class AsyncConferencesResource(AsyncAPIResource):
441
471
  self,
442
472
  id: str,
443
473
  *,
474
+ region: Literal["Australia", "Europe", "Middle East", "US"] | Omit = omit,
444
475
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
445
476
  # The extra values given here take precedence over values defined on the client or passed to this method.
446
477
  extra_headers: Headers | None = None,
@@ -452,6 +483,8 @@ class AsyncConferencesResource(AsyncAPIResource):
452
483
  Retrieve an existing conference
453
484
 
454
485
  Args:
486
+ region: Region where the conference data is located
487
+
455
488
  extra_headers: Send extra headers
456
489
 
457
490
  extra_query: Add additional query parameters to the request
@@ -465,7 +498,13 @@ class AsyncConferencesResource(AsyncAPIResource):
465
498
  return await self._get(
466
499
  f"/conferences/{id}",
467
500
  options=make_request_options(
468
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
501
+ extra_headers=extra_headers,
502
+ extra_query=extra_query,
503
+ extra_body=extra_body,
504
+ timeout=timeout,
505
+ query=await async_maybe_transform(
506
+ {"region": region}, conference_retrieve_params.ConferenceRetrieveParams
507
+ ),
469
508
  ),
470
509
  cast_to=ConferenceRetrieveResponse,
471
510
  )
@@ -475,6 +514,7 @@ class AsyncConferencesResource(AsyncAPIResource):
475
514
  *,
476
515
  filter: conference_list_params.Filter | Omit = omit,
477
516
  page: conference_list_params.Page | Omit = omit,
517
+ region: Literal["Australia", "Europe", "Middle East", "US"] | Omit = omit,
478
518
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
479
519
  # The extra values given here take precedence over values defined on the client or passed to this method.
480
520
  extra_headers: Headers | None = None,
@@ -500,6 +540,8 @@ class AsyncConferencesResource(AsyncAPIResource):
500
540
  page: Consolidated page parameter (deepObject style). Originally: page[after],
501
541
  page[before], page[limit], page[size], page[number]
502
542
 
543
+ region: Region where the conference data is located
544
+
503
545
  extra_headers: Send extra headers
504
546
 
505
547
  extra_query: Add additional query parameters to the request
@@ -519,6 +561,7 @@ class AsyncConferencesResource(AsyncAPIResource):
519
561
  {
520
562
  "filter": filter,
521
563
  "page": page,
564
+ "region": region,
522
565
  },
523
566
  conference_list_params.ConferenceListParams,
524
567
  ),
@@ -532,6 +575,7 @@ class AsyncConferencesResource(AsyncAPIResource):
532
575
  *,
533
576
  filter: conference_list_participants_params.Filter | Omit = omit,
534
577
  page: conference_list_participants_params.Page | Omit = omit,
578
+ region: Literal["Australia", "Europe", "Middle East", "US"] | Omit = omit,
535
579
  # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
536
580
  # The extra values given here take precedence over values defined on the client or passed to this method.
537
581
  extra_headers: Headers | None = None,
@@ -549,6 +593,8 @@ class AsyncConferencesResource(AsyncAPIResource):
549
593
  page: Consolidated page parameter (deepObject style). Originally: page[after],
550
594
  page[before], page[limit], page[size], page[number]
551
595
 
596
+ region: Region where the conference data is located
597
+
552
598
  extra_headers: Send extra headers
553
599
 
554
600
  extra_query: Add additional query parameters to the request
@@ -570,6 +616,7 @@ class AsyncConferencesResource(AsyncAPIResource):
570
616
  {
571
617
  "filter": filter,
572
618
  "page": page,
619
+ "region": region,
573
620
  },
574
621
  conference_list_participants_params.ConferenceListParticipantsParams,
575
622
  ),
@@ -108,7 +108,7 @@ class RecordingsResource(SyncAPIResource):
108
108
  Consolidated filter parameter (deepObject style). Originally:
109
109
  filter[conference_id], filter[created_at][gte], filter[created_at][lte],
110
110
  filter[call_leg_id], filter[call_session_id], filter[from], filter[to],
111
- filter[connection_id]
111
+ filter[connection_id], filter[sip_call_id]
112
112
 
113
113
  page: Consolidated page parameter (deepObject style). Originally: page[size],
114
114
  page[number]
@@ -250,7 +250,7 @@ class AsyncRecordingsResource(AsyncAPIResource):
250
250
  Consolidated filter parameter (deepObject style). Originally:
251
251
  filter[conference_id], filter[created_at][gte], filter[created_at][lte],
252
252
  filter[call_leg_id], filter[call_session_id], filter[from], filter[to],
253
- filter[connection_id]
253
+ filter[connection_id], filter[sip_call_id]
254
254
 
255
255
  page: Consolidated page parameter (deepObject style). Originally: page[size],
256
256
  page[number]
@@ -251,6 +251,7 @@ class CallsResource(SyncAPIResource):
251
251
  send_recording_url: bool | Omit = omit,
252
252
  sip_auth_password: str | Omit = omit,
253
253
  sip_auth_username: str | Omit = omit,
254
+ sip_region: Literal["US", "Europe", "Canada", "Australia", "Middle East"] | Omit = omit,
254
255
  status_callback: str | Omit = omit,
255
256
  status_callback_event: Literal["initiated", "ringing", "answered", "completed"] | Omit = omit,
256
257
  status_callback_method: Literal["GET", "POST"] | Omit = omit,
@@ -344,6 +345,8 @@ class CallsResource(SyncAPIResource):
344
345
 
345
346
  sip_auth_username: The username to use for SIP authentication.
346
347
 
348
+ sip_region: Defines the SIP region to be used for the call.
349
+
347
350
  status_callback: URL destination for Telnyx to send status callback events to for the call.
348
351
 
349
352
  status_callback_event: The call events for which Telnyx should send a webhook. Multiple events can be
@@ -401,6 +404,7 @@ class CallsResource(SyncAPIResource):
401
404
  "send_recording_url": send_recording_url,
402
405
  "sip_auth_password": sip_auth_password,
403
406
  "sip_auth_username": sip_auth_username,
407
+ "sip_region": sip_region,
404
408
  "status_callback": status_callback,
405
409
  "status_callback_event": status_callback_event,
406
410
  "status_callback_method": status_callback_method,
@@ -849,6 +853,7 @@ class AsyncCallsResource(AsyncAPIResource):
849
853
  send_recording_url: bool | Omit = omit,
850
854
  sip_auth_password: str | Omit = omit,
851
855
  sip_auth_username: str | Omit = omit,
856
+ sip_region: Literal["US", "Europe", "Canada", "Australia", "Middle East"] | Omit = omit,
852
857
  status_callback: str | Omit = omit,
853
858
  status_callback_event: Literal["initiated", "ringing", "answered", "completed"] | Omit = omit,
854
859
  status_callback_method: Literal["GET", "POST"] | Omit = omit,
@@ -942,6 +947,8 @@ class AsyncCallsResource(AsyncAPIResource):
942
947
 
943
948
  sip_auth_username: The username to use for SIP authentication.
944
949
 
950
+ sip_region: Defines the SIP region to be used for the call.
951
+
945
952
  status_callback: URL destination for Telnyx to send status callback events to for the call.
946
953
 
947
954
  status_callback_event: The call events for which Telnyx should send a webhook. Multiple events can be
@@ -999,6 +1006,7 @@ class AsyncCallsResource(AsyncAPIResource):
999
1006
  "send_recording_url": send_recording_url,
1000
1007
  "sip_auth_password": sip_auth_password,
1001
1008
  "sip_auth_username": sip_auth_username,
1009
+ "sip_region": sip_region,
1002
1010
  "status_callback": status_callback,
1003
1011
  "status_callback_event": status_callback_event,
1004
1012
  "status_callback_method": status_callback_method,
@@ -3,8 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
+ import time
7
+ import base64
6
8
  from typing import Mapping, cast
7
- from datetime import datetime, timezone
8
9
 
9
10
  from .._models import construct_type
10
11
  from .._resource import SyncAPIResource, AsyncAPIResource
@@ -12,106 +13,97 @@ from .._exceptions import TelnyxError
12
13
  from ..types.unwrap_webhook_event import UnwrapWebhookEvent
13
14
  from ..types.unsafe_unwrap_webhook_event import UnsafeUnwrapWebhookEvent
14
15
 
15
- __all__ = ["WebhooksResource", "AsyncWebhooksResource", "TelnyxWebhook", "TelnyxWebhookVerificationError"]
16
+ __all__ = ["WebhooksResource", "AsyncWebhooksResource", "TelnyxWebhookVerificationError"]
16
17
 
17
18
 
18
19
  class TelnyxWebhookVerificationError(TelnyxError):
19
- """Raised when webhook verification fails."""
20
+ """Raised when webhook signature verification fails."""
20
21
  pass
21
22
 
22
23
 
23
- class TelnyxWebhook:
24
+ def _get_header_case_insensitive(headers: dict[str, str], key: str) -> str | None:
25
+ """Get header value case-insensitively."""
26
+ key_lower = key.lower()
27
+ for header_key, header_value in headers.items():
28
+ if header_key.lower() == key_lower:
29
+ return header_value
30
+ return None
31
+
32
+
33
+ def _verify_signature(payload: str, headers: dict[str, str], public_key: str | bytes) -> None:
24
34
  """
25
- Telnyx webhook verification following the standardwebhooks pattern.
26
-
27
- This class provides ED25519 signature verification for Telnyx webhooks
28
- using the same interface pattern as the standardwebhooks library.
35
+ Verify Telnyx webhook signature using ED25519 cryptography.
36
+
37
+ Args:
38
+ payload: The raw webhook payload as a string
39
+ headers: The webhook headers
40
+ public_key: The ED25519 public key (base64 string from Mission Control)
41
+
42
+ Raises:
43
+ TelnyxWebhookVerificationError: If verification fails
29
44
  """
30
-
31
- def __init__(self, key: str | bytes):
32
- """
33
- Initialize the webhook verifier with a public key.
34
-
35
- Args:
36
- key: The public key for verification (hex string or bytes)
37
- """
38
- try:
39
- from nacl.signing import VerifyKey
40
- except ImportError as exc:
41
- raise TelnyxError("You need to install `pynacl` to verify Telnyx webhooks") from exc
42
-
43
- # Convert key to bytes if it's a string
44
- if isinstance(key, str):
45
- try:
46
- key = bytes.fromhex(key) # Convert from hex string to bytes
47
- except ValueError as exc:
48
- raise TelnyxWebhookVerificationError(f"Invalid key format: {key!r}") from exc
49
-
50
- self._verify_key = VerifyKey(key)
51
-
52
- def verify(self, payload: str, headers: Mapping[str, str]) -> None:
53
- """
54
- Verify a webhook payload and headers.
55
-
56
- Args:
57
- payload: The webhook payload string
58
- headers: The webhook headers
59
-
60
- Raises:
61
- TelnyxWebhookVerificationError: If verification fails
62
- """
63
- try:
64
- from nacl.exceptions import BadSignatureError
65
- except ImportError as exc:
66
- raise TelnyxError("You need to install `pynacl` to verify Telnyx webhooks") from exc
67
-
68
- # Extract required headers (case-insensitive lookup)
69
- signature_header = headers.get("Telnyx-Signature-Ed25519") or headers.get("telnyx-signature-ed25519")
70
- timestamp_header = headers.get("Telnyx-Timestamp") or headers.get("telnyx-timestamp")
71
- user_agent = headers.get("User-Agent") or headers.get("user-agent", "")
72
-
73
- # Validate required headers
74
- if not signature_header:
75
- raise TelnyxWebhookVerificationError("Missing required header: Telnyx-Signature-Ed25519")
76
-
77
- if not timestamp_header:
78
- raise TelnyxWebhookVerificationError("Missing required header: Telnyx-Timestamp")
79
-
80
- # Verify User-Agent if present (optional security check)
81
- if user_agent and "telnyx-webhooks" not in user_agent.lower():
82
- raise TelnyxWebhookVerificationError(f"Unexpected User-Agent: {user_agent}")
83
-
84
- # Validate timestamp format and prevent replay attacks
85
- try:
86
- webhook_time = int(timestamp_header)
87
- current_time = int(datetime.now(timezone.utc).timestamp())
88
-
89
- # Allow 5 minutes tolerance
90
- if abs(current_time - webhook_time) > 300:
91
- raise TelnyxWebhookVerificationError(
92
- f"Webhook timestamp too old or too new: {timestamp_header}"
93
- )
94
- except ValueError as exc:
45
+ try:
46
+ from nacl.signing import VerifyKey
47
+ from nacl.exceptions import BadSignatureError
48
+ except ImportError as exc:
49
+ raise TelnyxError("You need to install `pynacl` to use webhook verification") from exc
50
+
51
+ # Extract required headers (case-insensitive)
52
+ signature_header = _get_header_case_insensitive(headers, 'telnyx-signature-ed25519')
53
+ timestamp_header = _get_header_case_insensitive(headers, 'telnyx-timestamp')
54
+
55
+ if not signature_header:
56
+ raise TelnyxWebhookVerificationError("Missing header: Telnyx-Signature-Ed25519")
57
+
58
+ if not timestamp_header:
59
+ raise TelnyxWebhookVerificationError("Missing header: Telnyx-Timestamp")
60
+
61
+ # Validate timestamp to prevent replay attacks (5 minute tolerance)
62
+ try:
63
+ webhook_time = int(timestamp_header)
64
+ current_time = int(time.time())
65
+ if abs(current_time - webhook_time) > 300: # 5 minutes
66
+ raise TelnyxWebhookVerificationError(f"Webhook timestamp too old or too new: {timestamp_header}")
67
+ except ValueError as exc:
68
+ raise TelnyxWebhookVerificationError(f"Invalid timestamp format: {timestamp_header}") from exc
69
+
70
+ # Decode public key from base64 (Telnyx provides keys in base64 format)
71
+ try:
72
+ if isinstance(public_key, str):
73
+ public_key_bytes = base64.b64decode(public_key)
74
+ else:
75
+ public_key_bytes = public_key
76
+
77
+ if len(public_key_bytes) != 32:
95
78
  raise TelnyxWebhookVerificationError(
96
- f"Invalid timestamp format: {timestamp_header}"
97
- ) from exc
79
+ f"Invalid public key: expected 32 bytes, got {len(public_key_bytes)} bytes"
80
+ )
81
+
82
+ verify_key = VerifyKey(public_key_bytes)
83
+ except Exception as exc:
84
+ raise TelnyxWebhookVerificationError(f"Invalid public key format: {exc}") from exc
85
+
86
+ # Decode signature from base64 (Telnyx sends signatures in base64 format)
87
+ try:
88
+ signature_bytes = base64.b64decode(signature_header)
98
89
 
99
- # Decode the signature from hex
100
- try:
101
- signature = bytes.fromhex(signature_header)
102
- except ValueError as exc:
90
+ if len(signature_bytes) != 64:
103
91
  raise TelnyxWebhookVerificationError(
104
- f"Invalid signature format: {signature_header}"
105
- ) from exc
92
+ f"Invalid signature length: expected 64 bytes, got {len(signature_bytes)} bytes"
93
+ )
94
+ except Exception as exc:
95
+ raise TelnyxWebhookVerificationError(f"Invalid signature format: {exc}") from exc
106
96
 
107
- # Create the signed payload: timestamp + payload
108
- signed_payload = timestamp_header.encode('utf-8') + payload.encode('utf-8')
97
+ # Create the signed payload: timestamp|payload
98
+ signed_payload = f"{timestamp_header}|{payload}".encode('utf-8')
109
99
 
110
- # Verify the signature
111
- try:
112
- self._verify_key.verify(signed_payload, signature)
113
- except BadSignatureError as exc:
114
- raise TelnyxWebhookVerificationError("Invalid webhook signature") from exc
100
+ # Verify the signature
101
+ try:
102
+ verify_key.verify(signed_payload, signature_bytes)
103
+ except BadSignatureError as exc:
104
+ raise TelnyxWebhookVerificationError(
105
+ "Signature verification failed: signature does not match payload"
106
+ ) from exc
115
107
 
116
108
 
117
109
  class WebhooksResource(SyncAPIResource):
@@ -135,9 +127,7 @@ class WebhooksResource(SyncAPIResource):
135
127
  if not isinstance(headers, dict):
136
128
  headers = dict(headers)
137
129
 
138
- # Use Telnyx-specific webhook verification following standardwebhooks pattern
139
- webhook = TelnyxWebhook(key)
140
- webhook.verify(payload, headers)
130
+ _verify_signature(payload, headers, key)
141
131
 
142
132
  return cast(
143
133
  UnwrapWebhookEvent,
@@ -169,9 +159,7 @@ class AsyncWebhooksResource(AsyncAPIResource):
169
159
  if not isinstance(headers, dict):
170
160
  headers = dict(headers)
171
161
 
172
- # Use Telnyx-specific webhook verification following standardwebhooks pattern
173
- webhook = TelnyxWebhook(key)
174
- webhook.verify(payload, headers)
162
+ _verify_signature(payload, headers, key)
175
163
 
176
164
  return cast(
177
165
  UnwrapWebhookEvent,
telnyx/types/__init__.py CHANGED
@@ -322,6 +322,7 @@ from .call_bridged_webhook_event import CallBridgedWebhookEvent as CallBridgedWe
322
322
  from .channel_zone_list_response import ChannelZoneListResponse as ChannelZoneListResponse
323
323
  from .channel_zone_update_params import ChannelZoneUpdateParams as ChannelZoneUpdateParams
324
324
  from .conference_create_response import ConferenceCreateResponse as ConferenceCreateResponse
325
+ from .conference_retrieve_params import ConferenceRetrieveParams as ConferenceRetrieveParams
325
326
  from .document_retrieve_response import DocumentRetrieveResponse as DocumentRetrieveResponse
326
327
  from .dynamic_emergency_endpoint import DynamicEmergencyEndpoint as DynamicEmergencyEndpoint
327
328
  from .global_ip_assignment_param import GlobalIPAssignmentParam as GlobalIPAssignmentParam
@@ -35,6 +35,7 @@ from .embedding_list_params import EmbeddingListParams as EmbeddingListParams
35
35
  from .background_task_status import BackgroundTaskStatus as BackgroundTaskStatus
36
36
  from .cluster_compute_params import ClusterComputeParams as ClusterComputeParams
37
37
  from .insight_settings_param import InsightSettingsParam as InsightSettingsParam
38
+ from .mcp_server_list_params import McpServerListParams as McpServerListParams
38
39
  from .privacy_settings_param import PrivacySettingsParam as PrivacySettingsParam
39
40
  from .transcription_settings import TranscriptionSettings as TranscriptionSettings
40
41
  from .assistant_chat_response import AssistantChatResponse as AssistantChatResponse
@@ -48,21 +49,29 @@ from .embedding_list_response import EmbeddingListResponse as EmbeddingListRespo
48
49
  from .cluster_compute_response import ClusterComputeResponse as ClusterComputeResponse
49
50
  from .conversation_list_params import ConversationListParams as ConversationListParams
50
51
  from .hangup_tool_params_param import HangupToolParamsParam as HangupToolParamsParam
52
+ from .mcp_server_create_params import McpServerCreateParams as McpServerCreateParams
53
+ from .mcp_server_list_response import McpServerListResponse as McpServerListResponse
54
+ from .mcp_server_update_params import McpServerUpdateParams as McpServerUpdateParams
51
55
  from .messaging_settings_param import MessagingSettingsParam as MessagingSettingsParam
52
56
  from .telephony_settings_param import TelephonySettingsParam as TelephonySettingsParam
53
57
  from .assistant_delete_response import AssistantDeleteResponse as AssistantDeleteResponse
54
58
  from .assistant_retrieve_params import AssistantRetrieveParams as AssistantRetrieveParams
55
59
  from .audio_transcribe_response import AudioTranscribeResponse as AudioTranscribeResponse
56
60
  from .cluster_retrieve_response import ClusterRetrieveResponse as ClusterRetrieveResponse
61
+ from .integration_list_response import IntegrationListResponse as IntegrationListResponse
57
62
  from .cluster_fetch_graph_params import ClusterFetchGraphParams as ClusterFetchGraphParams
58
63
  from .conversation_create_params import ConversationCreateParams as ConversationCreateParams
59
64
  from .conversation_list_response import ConversationListResponse as ConversationListResponse
60
65
  from .conversation_update_params import ConversationUpdateParams as ConversationUpdateParams
66
+ from .mcp_server_create_response import McpServerCreateResponse as McpServerCreateResponse
67
+ from .mcp_server_update_response import McpServerUpdateResponse as McpServerUpdateResponse
61
68
  from .embedding_retrieve_response import EmbeddingRetrieveResponse as EmbeddingRetrieveResponse
62
69
  from .assistant_get_texml_response import AssistantGetTexmlResponse as AssistantGetTexmlResponse
63
70
  from .conversation_update_response import ConversationUpdateResponse as ConversationUpdateResponse
71
+ from .mcp_server_retrieve_response import McpServerRetrieveResponse as McpServerRetrieveResponse
64
72
  from .transcription_settings_param import TranscriptionSettingsParam as TranscriptionSettingsParam
65
73
  from .chat_create_completion_params import ChatCreateCompletionParams as ChatCreateCompletionParams
74
+ from .integration_retrieve_response import IntegrationRetrieveResponse as IntegrationRetrieveResponse
66
75
  from .conversation_retrieve_response import ConversationRetrieveResponse as ConversationRetrieveResponse
67
76
  from .inference_embedding_bucket_ids import InferenceEmbeddingBucketIDs as InferenceEmbeddingBucketIDs
68
77
  from .conversation_add_message_params import ConversationAddMessageParams as ConversationAddMessageParams
@@ -0,0 +1,28 @@
1
+ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
+
3
+ from typing import List
4
+ from typing_extensions import Literal
5
+
6
+ from ..._models import BaseModel
7
+
8
+ __all__ = ["IntegrationListResponse", "Data"]
9
+
10
+
11
+ class Data(BaseModel):
12
+ id: str
13
+
14
+ available_tools: List[str]
15
+
16
+ description: str
17
+
18
+ display_name: str
19
+
20
+ logo_url: str
21
+
22
+ name: str
23
+
24
+ status: Literal["disconnected", "connected"]
25
+
26
+
27
+ class IntegrationListResponse(BaseModel):
28
+ data: List[Data]
@@ -0,0 +1,24 @@
1
+ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
+
3
+ from typing import List
4
+ from typing_extensions import Literal
5
+
6
+ from ..._models import BaseModel
7
+
8
+ __all__ = ["IntegrationRetrieveResponse"]
9
+
10
+
11
+ class IntegrationRetrieveResponse(BaseModel):
12
+ id: str
13
+
14
+ available_tools: List[str]
15
+
16
+ description: str
17
+
18
+ display_name: str
19
+
20
+ logo_url: str
21
+
22
+ name: str
23
+
24
+ status: Literal["disconnected", "connected"]
@@ -0,0 +1,6 @@
1
+ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
+
3
+ from __future__ import annotations
4
+
5
+ from .connection_list_response import ConnectionListResponse as ConnectionListResponse
6
+ from .connection_retrieve_response import ConnectionRetrieveResponse as ConnectionRetrieveResponse
@@ -0,0 +1,19 @@
1
+ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
+
3
+ from typing import List
4
+
5
+ from ...._models import BaseModel
6
+
7
+ __all__ = ["ConnectionListResponse", "Data"]
8
+
9
+
10
+ class Data(BaseModel):
11
+ id: str
12
+
13
+ allowed_tools: List[str]
14
+
15
+ integration_id: str
16
+
17
+
18
+ class ConnectionListResponse(BaseModel):
19
+ data: List[Data]
@@ -0,0 +1,19 @@
1
+ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
+
3
+ from typing import List
4
+
5
+ from ...._models import BaseModel
6
+
7
+ __all__ = ["ConnectionRetrieveResponse", "Data"]
8
+
9
+
10
+ class Data(BaseModel):
11
+ id: str
12
+
13
+ allowed_tools: List[str]
14
+
15
+ integration_id: str
16
+
17
+
18
+ class ConnectionRetrieveResponse(BaseModel):
19
+ data: Data