telnyx 3.8.0__py3-none-any.whl → 3.9.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/_version.py +1 -1
- telnyx/resources/__init__.py +3 -1
- telnyx/resources/webhooks.py +107 -13
- {telnyx-3.8.0.dist-info → telnyx-3.9.0.dist-info}/METADATA +4 -2
- {telnyx-3.8.0.dist-info → telnyx-3.9.0.dist-info}/RECORD +7 -7
- {telnyx-3.8.0.dist-info → telnyx-3.9.0.dist-info}/WHEEL +0 -0
- {telnyx-3.8.0.dist-info → telnyx-3.9.0.dist-info}/licenses/LICENSE +0 -0
telnyx/_version.py
CHANGED
telnyx/resources/__init__.py
CHANGED
|
@@ -224,7 +224,7 @@ from .portouts import (
|
|
|
224
224
|
PortoutsResourceWithStreamingResponse,
|
|
225
225
|
AsyncPortoutsResourceWithStreamingResponse,
|
|
226
226
|
)
|
|
227
|
-
from .webhooks import WebhooksResource, AsyncWebhooksResource
|
|
227
|
+
from .webhooks import TelnyxWebhook, WebhooksResource, AsyncWebhooksResource, TelnyxWebhookVerificationError
|
|
228
228
|
from .wireless import (
|
|
229
229
|
WirelessResource,
|
|
230
230
|
AsyncWirelessResource,
|
|
@@ -1237,6 +1237,8 @@ __all__ = [
|
|
|
1237
1237
|
"AsyncOAuthGrantsResourceWithStreamingResponse",
|
|
1238
1238
|
"WebhooksResource",
|
|
1239
1239
|
"AsyncWebhooksResource",
|
|
1240
|
+
"TelnyxWebhook",
|
|
1241
|
+
"TelnyxWebhookVerificationError",
|
|
1240
1242
|
"AccessIPAddressResource",
|
|
1241
1243
|
"AsyncAccessIPAddressResource",
|
|
1242
1244
|
"AccessIPAddressResourceWithRawResponse",
|
telnyx/resources/webhooks.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
from typing import Mapping, cast
|
|
7
|
+
from datetime import datetime, timezone
|
|
7
8
|
|
|
8
9
|
from .._models import construct_type
|
|
9
10
|
from .._resource import SyncAPIResource, AsyncAPIResource
|
|
@@ -11,7 +12,106 @@ from .._exceptions import TelnyxError
|
|
|
11
12
|
from ..types.unwrap_webhook_event import UnwrapWebhookEvent
|
|
12
13
|
from ..types.unsafe_unwrap_webhook_event import UnsafeUnwrapWebhookEvent
|
|
13
14
|
|
|
14
|
-
__all__ = ["WebhooksResource", "AsyncWebhooksResource"]
|
|
15
|
+
__all__ = ["WebhooksResource", "AsyncWebhooksResource", "TelnyxWebhook", "TelnyxWebhookVerificationError"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TelnyxWebhookVerificationError(TelnyxError):
|
|
19
|
+
"""Raised when webhook verification fails."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TelnyxWebhook:
|
|
24
|
+
"""
|
|
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.
|
|
29
|
+
"""
|
|
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:
|
|
95
|
+
raise TelnyxWebhookVerificationError(
|
|
96
|
+
f"Invalid timestamp format: {timestamp_header}"
|
|
97
|
+
) from exc
|
|
98
|
+
|
|
99
|
+
# Decode the signature from hex
|
|
100
|
+
try:
|
|
101
|
+
signature = bytes.fromhex(signature_header)
|
|
102
|
+
except ValueError as exc:
|
|
103
|
+
raise TelnyxWebhookVerificationError(
|
|
104
|
+
f"Invalid signature format: {signature_header}"
|
|
105
|
+
) from exc
|
|
106
|
+
|
|
107
|
+
# Create the signed payload: timestamp + payload
|
|
108
|
+
signed_payload = timestamp_header.encode('utf-8') + payload.encode('utf-8')
|
|
109
|
+
|
|
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
|
|
15
115
|
|
|
16
116
|
|
|
17
117
|
class WebhooksResource(SyncAPIResource):
|
|
@@ -25,11 +125,6 @@ class WebhooksResource(SyncAPIResource):
|
|
|
25
125
|
)
|
|
26
126
|
|
|
27
127
|
def unwrap(self, payload: str, *, headers: Mapping[str, str], key: str | bytes | None = None) -> UnwrapWebhookEvent:
|
|
28
|
-
try:
|
|
29
|
-
from standardwebhooks import Webhook
|
|
30
|
-
except ImportError as exc:
|
|
31
|
-
raise TelnyxError("You need to install `telnyx[webhooks]` to use this method") from exc
|
|
32
|
-
|
|
33
128
|
if key is None:
|
|
34
129
|
key = self._client.public_key
|
|
35
130
|
if key is None:
|
|
@@ -40,7 +135,9 @@ class WebhooksResource(SyncAPIResource):
|
|
|
40
135
|
if not isinstance(headers, dict):
|
|
41
136
|
headers = dict(headers)
|
|
42
137
|
|
|
43
|
-
|
|
138
|
+
# Use Telnyx-specific webhook verification following standardwebhooks pattern
|
|
139
|
+
webhook = TelnyxWebhook(key)
|
|
140
|
+
webhook.verify(payload, headers)
|
|
44
141
|
|
|
45
142
|
return cast(
|
|
46
143
|
UnwrapWebhookEvent,
|
|
@@ -62,11 +159,6 @@ class AsyncWebhooksResource(AsyncAPIResource):
|
|
|
62
159
|
)
|
|
63
160
|
|
|
64
161
|
def unwrap(self, payload: str, *, headers: Mapping[str, str], key: str | bytes | None = None) -> UnwrapWebhookEvent:
|
|
65
|
-
try:
|
|
66
|
-
from standardwebhooks import Webhook
|
|
67
|
-
except ImportError as exc:
|
|
68
|
-
raise TelnyxError("You need to install `telnyx[webhooks]` to use this method") from exc
|
|
69
|
-
|
|
70
162
|
if key is None:
|
|
71
163
|
key = self._client.public_key
|
|
72
164
|
if key is None:
|
|
@@ -77,7 +169,9 @@ class AsyncWebhooksResource(AsyncAPIResource):
|
|
|
77
169
|
if not isinstance(headers, dict):
|
|
78
170
|
headers = dict(headers)
|
|
79
171
|
|
|
80
|
-
|
|
172
|
+
# Use Telnyx-specific webhook verification following standardwebhooks pattern
|
|
173
|
+
webhook = TelnyxWebhook(key)
|
|
174
|
+
webhook.verify(payload, headers)
|
|
81
175
|
|
|
82
176
|
return cast(
|
|
83
177
|
UnwrapWebhookEvent,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: telnyx
|
|
3
|
-
Version: 3.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 3.9.0
|
|
4
|
+
Summary: Telnyx API SDK for global Voice, SMS, MMS, WhatsApp, Fax, Wireless IoT, SIP Trunking, and Call Control.
|
|
5
5
|
Project-URL: Homepage, https://github.com/team-telnyx/telnyx-python
|
|
6
6
|
Project-URL: Repository, https://github.com/team-telnyx/telnyx-python
|
|
7
7
|
Author-email: Telnyx <support@telnyx.com>
|
|
8
8
|
License: MIT
|
|
9
|
+
Keywords: api,communications,connectivity,fax,iot,mms,sip,sms,telephony,telnyx,trunking,voice,voip,whatsapp
|
|
9
10
|
Classifier: Intended Audience :: Developers
|
|
10
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
12
|
Classifier: Operating System :: MacOS
|
|
@@ -32,6 +33,7 @@ Provides-Extra: aiohttp
|
|
|
32
33
|
Requires-Dist: aiohttp; extra == 'aiohttp'
|
|
33
34
|
Requires-Dist: httpx-aiohttp>=0.1.9; extra == 'aiohttp'
|
|
34
35
|
Provides-Extra: webhooks
|
|
36
|
+
Requires-Dist: pynacl>=1.5.0; extra == 'webhooks'
|
|
35
37
|
Requires-Dist: standardwebhooks; extra == 'webhooks'
|
|
36
38
|
Description-Content-Type: text/markdown
|
|
37
39
|
|
|
@@ -11,7 +11,7 @@ telnyx/_resource.py,sha256=B4Qg-uO2a34FQHHZskn89eVURqMuSvv1TdeBJH1z1rU,1100
|
|
|
11
11
|
telnyx/_response.py,sha256=4X24wr7uQn2hnM_b0xqQ92zSgxRFFfWG2lTg93-KzNo,28788
|
|
12
12
|
telnyx/_streaming.py,sha256=OfSFcMQJ_mnvfkbIdOG7Ajp0SMbXnOJSga4xXHjNAJk,10100
|
|
13
13
|
telnyx/_types.py,sha256=Du3G2vdqeLhhdJZ4Jtck4vOqEvAKI9rB1FnrwB1b_k8,7236
|
|
14
|
-
telnyx/_version.py,sha256=
|
|
14
|
+
telnyx/_version.py,sha256=yO7Mq2vTSOGHLfystQT0aBZqhiqyLnH7G4MvkMkg3l8,158
|
|
15
15
|
telnyx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
telnyx/_utils/__init__.py,sha256=7fch0GT9zpNnErbciSpUNa-SjTxxjY6kxHxKMOM4AGs,2305
|
|
17
17
|
telnyx/_utils/_compat.py,sha256=D8gtAvjJQrDWt9upS0XaG9Rr5l1QhiAx_I_1utT_tt0,1195
|
|
@@ -26,7 +26,7 @@ telnyx/_utils/_transform.py,sha256=NjCzmnfqYrsAikUHQig6N9QfuTVbKipuP3ur9mcNF-E,1
|
|
|
26
26
|
telnyx/_utils/_typing.py,sha256=N_5PPuFNsaygbtA_npZd98SVN1LQQvFTKL6bkWPBZGU,4786
|
|
27
27
|
telnyx/_utils/_utils.py,sha256=0dDqauUbVZEXV0NVl7Bwu904Wwo5eyFCZpQThhFNhyA,12253
|
|
28
28
|
telnyx/lib/.keep,sha256=wuNrz-5SXo3jJaJOJgz4vFHM41YH_g20F5cRQo0vLes,224
|
|
29
|
-
telnyx/resources/__init__.py,sha256=
|
|
29
|
+
telnyx/resources/__init__.py,sha256=kuejh4nn9SnMB6yXv44iqiub-rauIjl84hSFetcvG3M,86120
|
|
30
30
|
telnyx/resources/access_ip_address.py,sha256=GHrKaYv0pxX2NrECShHPxibCq1mpXXEOOTXFSTDNUVQ,17519
|
|
31
31
|
telnyx/resources/access_ip_ranges.py,sha256=Xb9w6OlrM6aphz9CfktBZDvespX13tLQWMiZFK46JN0,14210
|
|
32
32
|
telnyx/resources/advanced_orders.py,sha256=zHasSS2AwBk1K7GuxX5Vm9DjCbX5kwS3G-Q1M2p19vk,18707
|
|
@@ -134,7 +134,7 @@ telnyx/resources/verify_profiles.py,sha256=p04zuJk1_we5Ez6OyRVq-KE1EALdgVjzmXukG
|
|
|
134
134
|
telnyx/resources/virtual_cross_connects.py,sha256=JDAXB9dhVNn-qAiTyNDAgYELHdFwGoqS6yM5l4NrDZA,34715
|
|
135
135
|
telnyx/resources/virtual_cross_connects_coverage.py,sha256=LE0l4hJJCO8jsaanpA85fa_wGNHgsZO4yPpf1RoNRCQ,9320
|
|
136
136
|
telnyx/resources/webhook_deliveries.py,sha256=PKo-2i1Ndhe3i7zozog3d0Ko5dznDshsOqLeogdXah0,11174
|
|
137
|
-
telnyx/resources/webhooks.py,sha256=
|
|
137
|
+
telnyx/resources/webhooks.py,sha256=_6mEpkZNk9qYpRhbY6sPEqaiUkOiZjiCUo1JPBKOJys,6763
|
|
138
138
|
telnyx/resources/well_known.py,sha256=3Nm9gDqj9nDawaMoSj-yy3z_3dhDdBjK0NqOR0NJg4I,8881
|
|
139
139
|
telnyx/resources/wireguard_interfaces.py,sha256=wxfoFKlIQ4We6Zi9huXfN2Y_xAYZN5_IK6ziEGggM4I,18414
|
|
140
140
|
telnyx/resources/wireguard_peers.py,sha256=FefL6ivnxn6uq2vJY6hvzCzTm1wlqWiifc4D81QYSSE,24615
|
|
@@ -2096,7 +2096,7 @@ telnyx/types/wireless/detail_records_report_list_params.py,sha256=cfjsh4L_8mpDkg
|
|
|
2096
2096
|
telnyx/types/wireless/detail_records_report_list_response.py,sha256=S_6nD0fm5EseRIZHnML-UN0-g8Q_0J1cXfg_eLNUev8,331
|
|
2097
2097
|
telnyx/types/wireless/detail_records_report_retrieve_response.py,sha256=f0C8z8uo_QeCyi3nSDME4f4F3vqcy7o0MpinwDIqe_s,327
|
|
2098
2098
|
telnyx/types/wireless/wdr_report.py,sha256=bxRr-dc_IW6D0E3i_PUHK-bbu9w114Qql1uoJ_znxEE,1068
|
|
2099
|
-
telnyx-3.
|
|
2100
|
-
telnyx-3.
|
|
2101
|
-
telnyx-3.
|
|
2102
|
-
telnyx-3.
|
|
2099
|
+
telnyx-3.9.0.dist-info/METADATA,sha256=9Py7FhgBTlrRKMb8HgttIgL5imGDe79WNGvnaddz-bY,15823
|
|
2100
|
+
telnyx-3.9.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
2101
|
+
telnyx-3.9.0.dist-info/licenses/LICENSE,sha256=PprdXvskBJR41_t2uhgs5rHYGME_Ek-lh2PAxKtdZs8,1046
|
|
2102
|
+
telnyx-3.9.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|