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 CHANGED
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "telnyx"
4
- __version__ = "3.8.0" # x-release-please-version
4
+ __version__ = "3.9.0" # x-release-please-version
@@ -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",
@@ -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
- Webhook(key).verify(payload, headers)
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
- Webhook(key).verify(payload, headers)
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.8.0
4
- Summary: The official Python library for the telnyx API
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=lwpU2hAJbxRw1J5zKphT95CVY5KhP9FxxxnGrX2ydns,158
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=YdEPEfdK5zpqzCb5JF9PzIMDBqK9P14TUvHuDuQXVww,86014
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=Z25U-Umbj3DlGMsoSozv_ULe_dwmoL9oZ2FuuuQNkbA,2944
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.8.0.dist-info/METADATA,sha256=ODFtLiKVlZYhyzYvN7SI5CyEl3lra5IFqZK2W8rr2pM,15608
2100
- telnyx-3.8.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
2101
- telnyx-3.8.0.dist-info/licenses/LICENSE,sha256=PprdXvskBJR41_t2uhgs5rHYGME_Ek-lh2PAxKtdZs8,1046
2102
- telnyx-3.8.0.dist-info/RECORD,,
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