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.
- telnyx/_streaming.py +4 -6
- telnyx/_version.py +1 -1
- telnyx/resources/__init__.py +1 -2
- telnyx/resources/ai/__init__.py +28 -0
- telnyx/resources/ai/ai.py +64 -0
- telnyx/resources/ai/integrations/__init__.py +33 -0
- telnyx/resources/ai/integrations/connections.py +294 -0
- telnyx/resources/ai/integrations/integrations.py +246 -0
- telnyx/resources/ai/mcp_servers.py +571 -0
- telnyx/resources/calls/actions.py +8 -0
- telnyx/resources/calls/calls.py +8 -0
- telnyx/resources/conferences/actions.py +180 -8
- telnyx/resources/conferences/conferences.py +50 -3
- telnyx/resources/recordings/recordings.py +2 -2
- telnyx/resources/texml/accounts/calls/calls.py +8 -0
- telnyx/resources/webhooks.py +82 -94
- telnyx/types/__init__.py +1 -0
- telnyx/types/ai/__init__.py +9 -0
- telnyx/types/ai/integration_list_response.py +28 -0
- telnyx/types/ai/integration_retrieve_response.py +24 -0
- telnyx/types/ai/integrations/__init__.py +6 -0
- telnyx/types/ai/integrations/connection_list_response.py +19 -0
- telnyx/types/ai/integrations/connection_retrieve_response.py +19 -0
- telnyx/types/ai/mcp_server_create_params.py +22 -0
- telnyx/types/ai/mcp_server_create_response.py +24 -0
- telnyx/types/ai/mcp_server_list_params.py +19 -0
- telnyx/types/ai/mcp_server_list_response.py +28 -0
- telnyx/types/ai/mcp_server_retrieve_response.py +24 -0
- telnyx/types/ai/mcp_server_update_params.py +28 -0
- telnyx/types/ai/mcp_server_update_response.py +24 -0
- telnyx/types/call_dial_params.py +3 -0
- telnyx/types/calls/action_transfer_params.py +3 -0
- telnyx/types/conference_create_params.py +6 -0
- telnyx/types/conference_list_params.py +3 -0
- telnyx/types/conference_list_participants_params.py +4 -1
- telnyx/types/conference_retrieve_params.py +12 -0
- telnyx/types/conferences/action_hold_params.py +7 -1
- telnyx/types/conferences/action_join_params.py +6 -0
- telnyx/types/conferences/action_leave_params.py +6 -0
- telnyx/types/conferences/action_mute_params.py +7 -1
- telnyx/types/conferences/action_play_params.py +7 -1
- telnyx/types/conferences/action_record_pause_params.py +7 -1
- telnyx/types/conferences/action_record_resume_params.py +7 -1
- telnyx/types/conferences/action_record_start_params.py +6 -0
- telnyx/types/conferences/action_record_stop_params.py +7 -1
- telnyx/types/conferences/action_speak_params.py +6 -0
- telnyx/types/conferences/action_stop_params.py +7 -1
- telnyx/types/conferences/action_unhold_params.py +7 -1
- telnyx/types/conferences/action_unmute_params.py +7 -1
- telnyx/types/conferences/action_update_params.py +6 -0
- telnyx/types/recording_list_params.py +7 -1
- telnyx/types/texml/accounts/call_calls_params.py +5 -0
- {telnyx-3.9.0.dist-info → telnyx-3.11.0.dist-info}/METADATA +1 -1
- {telnyx-3.9.0.dist-info → telnyx-3.11.0.dist-info}/RECORD +56 -39
- {telnyx-3.9.0.dist-info → telnyx-3.11.0.dist-info}/WHEEL +0 -0
- {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
|
|
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,
|
|
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,
|
|
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,
|
telnyx/resources/webhooks.py
CHANGED
|
@@ -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", "
|
|
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
|
-
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
97
|
-
)
|
|
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
|
-
|
|
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
|
|
105
|
-
)
|
|
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
|
-
|
|
108
|
-
|
|
97
|
+
# Create the signed payload: timestamp|payload
|
|
98
|
+
signed_payload = f"{timestamp_header}|{payload}".encode('utf-8')
|
|
109
99
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
telnyx/types/ai/__init__.py
CHANGED
|
@@ -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
|