linq-python 0.1.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.
Files changed (139) hide show
  1. linq/__init__.py +102 -0
  2. linq/_base_client.py +2149 -0
  3. linq/_client.py +2479 -0
  4. linq/_compat.py +226 -0
  5. linq/_constants.py +14 -0
  6. linq/_exceptions.py +108 -0
  7. linq/_files.py +123 -0
  8. linq/_models.py +878 -0
  9. linq/_qs.py +153 -0
  10. linq/_resource.py +43 -0
  11. linq/_response.py +833 -0
  12. linq/_streaming.py +338 -0
  13. linq/_types.py +271 -0
  14. linq/_utils/__init__.py +65 -0
  15. linq/_utils/_compat.py +45 -0
  16. linq/_utils/_datetime_parse.py +136 -0
  17. linq/_utils/_json.py +35 -0
  18. linq/_utils/_logs.py +25 -0
  19. linq/_utils/_path.py +127 -0
  20. linq/_utils/_proxy.py +65 -0
  21. linq/_utils/_reflection.py +42 -0
  22. linq/_utils/_resources_proxy.py +24 -0
  23. linq/_utils/_streams.py +12 -0
  24. linq/_utils/_sync.py +58 -0
  25. linq/_utils/_transform.py +457 -0
  26. linq/_utils/_typing.py +156 -0
  27. linq/_utils/_utils.py +421 -0
  28. linq/_version.py +4 -0
  29. linq/lib/.keep +4 -0
  30. linq/pagination.py +95 -0
  31. linq/py.typed +0 -0
  32. linq/resources/__init__.py +134 -0
  33. linq/resources/attachments.py +589 -0
  34. linq/resources/capability.py +297 -0
  35. linq/resources/chats/__init__.py +61 -0
  36. linq/resources/chats/chats.py +1492 -0
  37. linq/resources/chats/messages.py +416 -0
  38. linq/resources/chats/participants.py +322 -0
  39. linq/resources/chats/typing.py +299 -0
  40. linq/resources/contact_card.py +472 -0
  41. linq/resources/messages.py +686 -0
  42. linq/resources/phone_numbers.py +163 -0
  43. linq/resources/phonenumbers.py +165 -0
  44. linq/resources/webhook_events.py +319 -0
  45. linq/resources/webhook_subscriptions.py +776 -0
  46. linq/resources/webhooks.py +34 -0
  47. linq/types/__init__.py +90 -0
  48. linq/types/attachment_create_params.py +42 -0
  49. linq/types/attachment_create_response.py +44 -0
  50. linq/types/attachment_retrieve_response.py +55 -0
  51. linq/types/capability_check_RCS_params.py +20 -0
  52. linq/types/capability_check_i_message_params.py +20 -0
  53. linq/types/chat.py +44 -0
  54. linq/types/chat_create_params.py +33 -0
  55. linq/types/chat_create_response.py +44 -0
  56. linq/types/chat_created_webhook_event.py +87 -0
  57. linq/types/chat_group_icon_update_failed_webhook_event.py +65 -0
  58. linq/types/chat_group_icon_updated_webhook_event.py +66 -0
  59. linq/types/chat_group_name_update_failed_webhook_event.py +65 -0
  60. linq/types/chat_group_name_updated_webhook_event.py +66 -0
  61. linq/types/chat_leave_chat_response.py +15 -0
  62. linq/types/chat_list_chats_params.py +36 -0
  63. linq/types/chat_send_voicememo_params.py +23 -0
  64. linq/types/chat_send_voicememo_response.py +79 -0
  65. linq/types/chat_typing_indicator_started_webhook_event.py +52 -0
  66. linq/types/chat_typing_indicator_stopped_webhook_event.py +52 -0
  67. linq/types/chat_update_params.py +15 -0
  68. linq/types/chat_update_response.py +13 -0
  69. linq/types/chats/__init__.py +12 -0
  70. linq/types/chats/message_list_params.py +15 -0
  71. linq/types/chats/message_send_params.py +18 -0
  72. linq/types/chats/message_send_response.py +16 -0
  73. linq/types/chats/participant_add_params.py +12 -0
  74. linq/types/chats/participant_add_response.py +15 -0
  75. linq/types/chats/participant_remove_params.py +12 -0
  76. linq/types/chats/participant_remove_response.py +15 -0
  77. linq/types/chats/sent_message.py +69 -0
  78. linq/types/contact_card_create_params.py +24 -0
  79. linq/types/contact_card_retrieve_params.py +15 -0
  80. linq/types/contact_card_retrieve_response.py +23 -0
  81. linq/types/contact_card_update_params.py +21 -0
  82. linq/types/events_webhook_event.py +50 -0
  83. linq/types/handle_check_response.py +13 -0
  84. linq/types/link_part_param.py +22 -0
  85. linq/types/media_part_param.py +54 -0
  86. linq/types/message.py +87 -0
  87. linq/types/message_add_reaction_params.py +32 -0
  88. linq/types/message_add_reaction_response.py +15 -0
  89. linq/types/message_content_param.py +82 -0
  90. linq/types/message_delivered_webhook_event.py +65 -0
  91. linq/types/message_edited_webhook_event.py +100 -0
  92. linq/types/message_effect.py +23 -0
  93. linq/types/message_effect_param.py +22 -0
  94. linq/types/message_event_v2.py +116 -0
  95. linq/types/message_failed_webhook_event.py +72 -0
  96. linq/types/message_list_messages_thread_params.py +18 -0
  97. linq/types/message_read_webhook_event.py +65 -0
  98. linq/types/message_received_webhook_event.py +65 -0
  99. linq/types/message_sent_webhook_event.py +65 -0
  100. linq/types/message_update_params.py +15 -0
  101. linq/types/participant_added_webhook_event.py +66 -0
  102. linq/types/participant_removed_webhook_event.py +66 -0
  103. linq/types/phone_number_list_response.py +20 -0
  104. linq/types/phone_number_status_updated_webhook_event.py +82 -0
  105. linq/types/phonenumber_list_response.py +39 -0
  106. linq/types/reaction_added_webhook_event.py +46 -0
  107. linq/types/reaction_event_base.py +85 -0
  108. linq/types/reaction_removed_webhook_event.py +46 -0
  109. linq/types/reply_to.py +21 -0
  110. linq/types/reply_to_param.py +21 -0
  111. linq/types/schemas_media_part_response.py +29 -0
  112. linq/types/schemas_message_effect.py +18 -0
  113. linq/types/schemas_text_part_response.py +22 -0
  114. linq/types/set_contact_card.py +24 -0
  115. linq/types/shared/__init__.py +9 -0
  116. linq/types/shared/chat_handle.py +33 -0
  117. linq/types/shared/media_part_response.py +34 -0
  118. linq/types/shared/reaction.py +56 -0
  119. linq/types/shared/reaction_type.py +7 -0
  120. linq/types/shared/service_type.py +7 -0
  121. linq/types/shared/text_decoration.py +23 -0
  122. linq/types/shared/text_part_response.py +26 -0
  123. linq/types/shared_params/__init__.py +5 -0
  124. linq/types/shared_params/reaction_type.py +9 -0
  125. linq/types/shared_params/service_type.py +9 -0
  126. linq/types/shared_params/text_decoration.py +23 -0
  127. linq/types/supported_content_type.py +60 -0
  128. linq/types/text_part_param.py +44 -0
  129. linq/types/webhook_event_list_response.py +17 -0
  130. linq/types/webhook_event_type.py +33 -0
  131. linq/types/webhook_subscription.py +35 -0
  132. linq/types/webhook_subscription_create_params.py +27 -0
  133. linq/types/webhook_subscription_create_response.py +46 -0
  134. linq/types/webhook_subscription_list_response.py +13 -0
  135. linq/types/webhook_subscription_update_params.py +30 -0
  136. linq_python-0.1.0.dist-info/METADATA +572 -0
  137. linq_python-0.1.0.dist-info/RECORD +139 -0
  138. linq_python-0.1.0.dist-info/WHEEL +4 -0
  139. linq_python-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,776 @@
1
+ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import List, Optional
6
+
7
+ import httpx
8
+
9
+ from ..types import webhook_subscription_create_params, webhook_subscription_update_params
10
+ from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
11
+ from .._utils import path_template, maybe_transform, async_maybe_transform
12
+ from .._compat import cached_property
13
+ from .._resource import SyncAPIResource, AsyncAPIResource
14
+ from .._response import (
15
+ to_raw_response_wrapper,
16
+ to_streamed_response_wrapper,
17
+ async_to_raw_response_wrapper,
18
+ async_to_streamed_response_wrapper,
19
+ )
20
+ from .._base_client import make_request_options
21
+ from ..types.webhook_event_type import WebhookEventType
22
+ from ..types.webhook_subscription import WebhookSubscription
23
+ from ..types.webhook_subscription_list_response import WebhookSubscriptionListResponse
24
+ from ..types.webhook_subscription_create_response import WebhookSubscriptionCreateResponse
25
+
26
+ __all__ = ["WebhookSubscriptionsResource", "AsyncWebhookSubscriptionsResource"]
27
+
28
+
29
+ class WebhookSubscriptionsResource(SyncAPIResource):
30
+ """
31
+ Webhook Subscriptions allow you to receive real-time notifications when events
32
+ occur on your account.
33
+
34
+ Configure webhook endpoints to receive events such as messages sent/received,
35
+ delivery status changes, reactions, typing indicators, and more.
36
+
37
+ Failed deliveries (5xx, 429, network errors) are retried up to 10 times over
38
+ ~25 minutes with exponential backoff. Each event includes a unique ID for
39
+ deduplication.
40
+
41
+ ## Webhook Headers
42
+
43
+ Each webhook request includes the following headers:
44
+
45
+ | Header | Description |
46
+ |--------|-------------|
47
+ | `X-Webhook-Event` | The event type (e.g., `message.sent`, `message.received`) |
48
+ | `X-Webhook-Subscription-ID` | Your webhook subscription ID |
49
+ | `X-Webhook-Timestamp` | Unix timestamp (seconds) when the webhook was sent |
50
+ | `X-Webhook-Signature` | HMAC-SHA256 signature for verification |
51
+
52
+ ## Verifying Webhook Signatures
53
+
54
+ All webhooks are signed using HMAC-SHA256. You should always verify the signature
55
+ to ensure the webhook originated from Linq and hasn't been tampered with.
56
+
57
+ **Signature Construction:**
58
+
59
+ The signature is computed over a concatenation of the timestamp and payload:
60
+
61
+ ```
62
+ {timestamp}.{payload}
63
+ ```
64
+
65
+ Where:
66
+ - `timestamp` is the value from the `X-Webhook-Timestamp` header
67
+ - `payload` is the raw JSON request body (exact bytes, not re-serialized)
68
+
69
+ **Verification Steps:**
70
+
71
+ 1. Extract the `X-Webhook-Timestamp` and `X-Webhook-Signature` headers
72
+ 2. Get the raw request body bytes (do not parse and re-serialize)
73
+ 3. Concatenate: `"{timestamp}.{payload}"`
74
+ 4. Compute HMAC-SHA256 using your signing secret as the key
75
+ 5. Hex-encode the result and compare with `X-Webhook-Signature`
76
+ 6. Use constant-time comparison to prevent timing attacks
77
+
78
+ **Example (Python):**
79
+
80
+ ```python
81
+ import hmac
82
+ import hashlib
83
+
84
+
85
+ def verify_webhook(signing_secret, payload, timestamp, signature):
86
+ message = f"{timestamp}.{payload.decode('utf-8')}"
87
+ expected = hmac.new(signing_secret.encode("utf-8"), message.encode("utf-8"), hashlib.sha256).hexdigest()
88
+ return hmac.compare_digest(expected, signature)
89
+ ```
90
+
91
+ **Example (Node.js):**
92
+
93
+ ```javascript
94
+ const crypto = require('crypto');
95
+
96
+ function verifyWebhook(signingSecret, payload, timestamp, signature) {
97
+ const message = `${timestamp}.${payload}`;
98
+ const expected = crypto
99
+ .createHmac('sha256', signingSecret)
100
+ .update(message)
101
+ .digest('hex');
102
+ return crypto.timingSafeEqual(
103
+ Buffer.from(expected),
104
+ Buffer.from(signature)
105
+ );
106
+ }
107
+ ```
108
+
109
+ **Security Best Practices:**
110
+
111
+ - Reject webhooks with timestamps older than 5 minutes to prevent replay attacks
112
+ - Always use constant-time comparison for signature verification
113
+ - Store your signing secret securely (e.g., environment variable, secrets manager)
114
+ - Return a 2xx status code quickly, then process the webhook asynchronously
115
+ """
116
+
117
+ @cached_property
118
+ def with_raw_response(self) -> WebhookSubscriptionsResourceWithRawResponse:
119
+ """
120
+ This property can be used as a prefix for any HTTP method call to return
121
+ the raw response object instead of the parsed content.
122
+
123
+ For more information, see https://www.github.com/linq-team/linq-python#accessing-raw-response-data-eg-headers
124
+ """
125
+ return WebhookSubscriptionsResourceWithRawResponse(self)
126
+
127
+ @cached_property
128
+ def with_streaming_response(self) -> WebhookSubscriptionsResourceWithStreamingResponse:
129
+ """
130
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
131
+
132
+ For more information, see https://www.github.com/linq-team/linq-python#with_streaming_response
133
+ """
134
+ return WebhookSubscriptionsResourceWithStreamingResponse(self)
135
+
136
+ def create(
137
+ self,
138
+ *,
139
+ subscribed_events: List[WebhookEventType],
140
+ target_url: str,
141
+ phone_numbers: SequenceNotStr[str] | Omit = omit,
142
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
143
+ # The extra values given here take precedence over values defined on the client or passed to this method.
144
+ extra_headers: Headers | None = None,
145
+ extra_query: Query | None = None,
146
+ extra_body: Body | None = None,
147
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
148
+ ) -> WebhookSubscriptionCreateResponse:
149
+ """Create a new webhook subscription to receive events at a target URL.
150
+
151
+ Upon
152
+ creation, a signing secret is generated for verifying webhook authenticity.
153
+ **Store this secret securely — it cannot be retrieved later.**
154
+
155
+ **Phone Number Filtering:**
156
+
157
+ - Optionally specify `phone_numbers` to only receive events for specific lines
158
+ - If omitted, events from all phone numbers are delivered (default behavior)
159
+ - Use multiple subscriptions with different `phone_numbers` to route different
160
+ lines to different endpoints
161
+ - Each `target_url` can only be used once per account. To route different lines
162
+ to different destinations, use a unique URL per subscription (e.g., append a
163
+ query parameter: `https://example.com/webhook?line=1`)
164
+
165
+ **Webhook Delivery:**
166
+
167
+ - Events are sent via HTTP POST to the target URL
168
+ - Each request includes `X-Webhook-Signature` and `X-Webhook-Timestamp` headers
169
+ - Signature is HMAC-SHA256 over `{timestamp}.{payload}` — see
170
+ [Webhook Events](/docs/webhook-events) for verification details
171
+ - Failed deliveries (5xx, 429, network errors) are retried up to 10 times over
172
+ ~25 minutes with exponential backoff
173
+ - Client errors (4xx except 429) are not retried
174
+
175
+ Args:
176
+ subscribed_events: List of event types to subscribe to
177
+
178
+ target_url: URL where webhook events will be sent. Must be HTTPS.
179
+
180
+ phone_numbers: Optional list of phone numbers to filter events for. Only events originating
181
+ from these phone numbers will be delivered to this subscription. If omitted or
182
+ empty, events from all phone numbers are delivered. Phone numbers must be in
183
+ E.164 format.
184
+
185
+ extra_headers: Send extra headers
186
+
187
+ extra_query: Add additional query parameters to the request
188
+
189
+ extra_body: Add additional JSON properties to the request
190
+
191
+ timeout: Override the client-level default timeout for this request, in seconds
192
+ """
193
+ return self._post(
194
+ "/v3/webhook-subscriptions",
195
+ body=maybe_transform(
196
+ {
197
+ "subscribed_events": subscribed_events,
198
+ "target_url": target_url,
199
+ "phone_numbers": phone_numbers,
200
+ },
201
+ webhook_subscription_create_params.WebhookSubscriptionCreateParams,
202
+ ),
203
+ options=make_request_options(
204
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
205
+ ),
206
+ cast_to=WebhookSubscriptionCreateResponse,
207
+ )
208
+
209
+ def retrieve(
210
+ self,
211
+ subscription_id: str,
212
+ *,
213
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
214
+ # The extra values given here take precedence over values defined on the client or passed to this method.
215
+ extra_headers: Headers | None = None,
216
+ extra_query: Query | None = None,
217
+ extra_body: Body | None = None,
218
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
219
+ ) -> WebhookSubscription:
220
+ """
221
+ Retrieve details for a specific webhook subscription including its target URL,
222
+ subscribed events, and current status.
223
+
224
+ Args:
225
+ extra_headers: Send extra headers
226
+
227
+ extra_query: Add additional query parameters to the request
228
+
229
+ extra_body: Add additional JSON properties to the request
230
+
231
+ timeout: Override the client-level default timeout for this request, in seconds
232
+ """
233
+ if not subscription_id:
234
+ raise ValueError(f"Expected a non-empty value for `subscription_id` but received {subscription_id!r}")
235
+ return self._get(
236
+ path_template("/v3/webhook-subscriptions/{subscription_id}", subscription_id=subscription_id),
237
+ options=make_request_options(
238
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
239
+ ),
240
+ cast_to=WebhookSubscription,
241
+ )
242
+
243
+ def update(
244
+ self,
245
+ subscription_id: str,
246
+ *,
247
+ is_active: bool | Omit = omit,
248
+ phone_numbers: Optional[SequenceNotStr[str]] | Omit = omit,
249
+ subscribed_events: List[WebhookEventType] | Omit = omit,
250
+ target_url: str | Omit = omit,
251
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
252
+ # The extra values given here take precedence over values defined on the client or passed to this method.
253
+ extra_headers: Headers | None = None,
254
+ extra_query: Query | None = None,
255
+ extra_body: Body | None = None,
256
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
257
+ ) -> WebhookSubscription:
258
+ """Update an existing webhook subscription.
259
+
260
+ You can modify the target URL,
261
+ subscribed events, or activate/deactivate the subscription.
262
+
263
+ **Note:** The signing secret cannot be changed via this endpoint.
264
+
265
+ Args:
266
+ is_active: Activate or deactivate the subscription
267
+
268
+ phone_numbers: Updated list of phone numbers to filter events for. Set to a non-empty array to
269
+ filter events to specific phone numbers. Set to an empty array or null to remove
270
+ the filter and receive events from all phone numbers. Phone numbers must be in
271
+ E.164 format.
272
+
273
+ subscribed_events: Updated list of event types to subscribe to
274
+
275
+ target_url: New target URL for webhook events
276
+
277
+ extra_headers: Send extra headers
278
+
279
+ extra_query: Add additional query parameters to the request
280
+
281
+ extra_body: Add additional JSON properties to the request
282
+
283
+ timeout: Override the client-level default timeout for this request, in seconds
284
+ """
285
+ if not subscription_id:
286
+ raise ValueError(f"Expected a non-empty value for `subscription_id` but received {subscription_id!r}")
287
+ return self._put(
288
+ path_template("/v3/webhook-subscriptions/{subscription_id}", subscription_id=subscription_id),
289
+ body=maybe_transform(
290
+ {
291
+ "is_active": is_active,
292
+ "phone_numbers": phone_numbers,
293
+ "subscribed_events": subscribed_events,
294
+ "target_url": target_url,
295
+ },
296
+ webhook_subscription_update_params.WebhookSubscriptionUpdateParams,
297
+ ),
298
+ options=make_request_options(
299
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
300
+ ),
301
+ cast_to=WebhookSubscription,
302
+ )
303
+
304
+ def list(
305
+ self,
306
+ *,
307
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
308
+ # The extra values given here take precedence over values defined on the client or passed to this method.
309
+ extra_headers: Headers | None = None,
310
+ extra_query: Query | None = None,
311
+ extra_body: Body | None = None,
312
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
313
+ ) -> WebhookSubscriptionListResponse:
314
+ """Retrieve all webhook subscriptions for the authenticated partner.
315
+
316
+ Returns a list
317
+ of active and inactive subscriptions with their configuration and status.
318
+ """
319
+ return self._get(
320
+ "/v3/webhook-subscriptions",
321
+ options=make_request_options(
322
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
323
+ ),
324
+ cast_to=WebhookSubscriptionListResponse,
325
+ )
326
+
327
+ def delete(
328
+ self,
329
+ subscription_id: str,
330
+ *,
331
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
332
+ # The extra values given here take precedence over values defined on the client or passed to this method.
333
+ extra_headers: Headers | None = None,
334
+ extra_query: Query | None = None,
335
+ extra_body: Body | None = None,
336
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
337
+ ) -> None:
338
+ """
339
+ Delete a webhook subscription.
340
+
341
+ Args:
342
+ extra_headers: Send extra headers
343
+
344
+ extra_query: Add additional query parameters to the request
345
+
346
+ extra_body: Add additional JSON properties to the request
347
+
348
+ timeout: Override the client-level default timeout for this request, in seconds
349
+ """
350
+ if not subscription_id:
351
+ raise ValueError(f"Expected a non-empty value for `subscription_id` but received {subscription_id!r}")
352
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
353
+ return self._delete(
354
+ path_template("/v3/webhook-subscriptions/{subscription_id}", subscription_id=subscription_id),
355
+ options=make_request_options(
356
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
357
+ ),
358
+ cast_to=NoneType,
359
+ )
360
+
361
+
362
+ class AsyncWebhookSubscriptionsResource(AsyncAPIResource):
363
+ """
364
+ Webhook Subscriptions allow you to receive real-time notifications when events
365
+ occur on your account.
366
+
367
+ Configure webhook endpoints to receive events such as messages sent/received,
368
+ delivery status changes, reactions, typing indicators, and more.
369
+
370
+ Failed deliveries (5xx, 429, network errors) are retried up to 10 times over
371
+ ~25 minutes with exponential backoff. Each event includes a unique ID for
372
+ deduplication.
373
+
374
+ ## Webhook Headers
375
+
376
+ Each webhook request includes the following headers:
377
+
378
+ | Header | Description |
379
+ |--------|-------------|
380
+ | `X-Webhook-Event` | The event type (e.g., `message.sent`, `message.received`) |
381
+ | `X-Webhook-Subscription-ID` | Your webhook subscription ID |
382
+ | `X-Webhook-Timestamp` | Unix timestamp (seconds) when the webhook was sent |
383
+ | `X-Webhook-Signature` | HMAC-SHA256 signature for verification |
384
+
385
+ ## Verifying Webhook Signatures
386
+
387
+ All webhooks are signed using HMAC-SHA256. You should always verify the signature
388
+ to ensure the webhook originated from Linq and hasn't been tampered with.
389
+
390
+ **Signature Construction:**
391
+
392
+ The signature is computed over a concatenation of the timestamp and payload:
393
+
394
+ ```
395
+ {timestamp}.{payload}
396
+ ```
397
+
398
+ Where:
399
+ - `timestamp` is the value from the `X-Webhook-Timestamp` header
400
+ - `payload` is the raw JSON request body (exact bytes, not re-serialized)
401
+
402
+ **Verification Steps:**
403
+
404
+ 1. Extract the `X-Webhook-Timestamp` and `X-Webhook-Signature` headers
405
+ 2. Get the raw request body bytes (do not parse and re-serialize)
406
+ 3. Concatenate: `"{timestamp}.{payload}"`
407
+ 4. Compute HMAC-SHA256 using your signing secret as the key
408
+ 5. Hex-encode the result and compare with `X-Webhook-Signature`
409
+ 6. Use constant-time comparison to prevent timing attacks
410
+
411
+ **Example (Python):**
412
+
413
+ ```python
414
+ import hmac
415
+ import hashlib
416
+
417
+
418
+ def verify_webhook(signing_secret, payload, timestamp, signature):
419
+ message = f"{timestamp}.{payload.decode('utf-8')}"
420
+ expected = hmac.new(signing_secret.encode("utf-8"), message.encode("utf-8"), hashlib.sha256).hexdigest()
421
+ return hmac.compare_digest(expected, signature)
422
+ ```
423
+
424
+ **Example (Node.js):**
425
+
426
+ ```javascript
427
+ const crypto = require('crypto');
428
+
429
+ function verifyWebhook(signingSecret, payload, timestamp, signature) {
430
+ const message = `${timestamp}.${payload}`;
431
+ const expected = crypto
432
+ .createHmac('sha256', signingSecret)
433
+ .update(message)
434
+ .digest('hex');
435
+ return crypto.timingSafeEqual(
436
+ Buffer.from(expected),
437
+ Buffer.from(signature)
438
+ );
439
+ }
440
+ ```
441
+
442
+ **Security Best Practices:**
443
+
444
+ - Reject webhooks with timestamps older than 5 minutes to prevent replay attacks
445
+ - Always use constant-time comparison for signature verification
446
+ - Store your signing secret securely (e.g., environment variable, secrets manager)
447
+ - Return a 2xx status code quickly, then process the webhook asynchronously
448
+ """
449
+
450
+ @cached_property
451
+ def with_raw_response(self) -> AsyncWebhookSubscriptionsResourceWithRawResponse:
452
+ """
453
+ This property can be used as a prefix for any HTTP method call to return
454
+ the raw response object instead of the parsed content.
455
+
456
+ For more information, see https://www.github.com/linq-team/linq-python#accessing-raw-response-data-eg-headers
457
+ """
458
+ return AsyncWebhookSubscriptionsResourceWithRawResponse(self)
459
+
460
+ @cached_property
461
+ def with_streaming_response(self) -> AsyncWebhookSubscriptionsResourceWithStreamingResponse:
462
+ """
463
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
464
+
465
+ For more information, see https://www.github.com/linq-team/linq-python#with_streaming_response
466
+ """
467
+ return AsyncWebhookSubscriptionsResourceWithStreamingResponse(self)
468
+
469
+ async def create(
470
+ self,
471
+ *,
472
+ subscribed_events: List[WebhookEventType],
473
+ target_url: str,
474
+ phone_numbers: SequenceNotStr[str] | Omit = omit,
475
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
476
+ # The extra values given here take precedence over values defined on the client or passed to this method.
477
+ extra_headers: Headers | None = None,
478
+ extra_query: Query | None = None,
479
+ extra_body: Body | None = None,
480
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
481
+ ) -> WebhookSubscriptionCreateResponse:
482
+ """Create a new webhook subscription to receive events at a target URL.
483
+
484
+ Upon
485
+ creation, a signing secret is generated for verifying webhook authenticity.
486
+ **Store this secret securely — it cannot be retrieved later.**
487
+
488
+ **Phone Number Filtering:**
489
+
490
+ - Optionally specify `phone_numbers` to only receive events for specific lines
491
+ - If omitted, events from all phone numbers are delivered (default behavior)
492
+ - Use multiple subscriptions with different `phone_numbers` to route different
493
+ lines to different endpoints
494
+ - Each `target_url` can only be used once per account. To route different lines
495
+ to different destinations, use a unique URL per subscription (e.g., append a
496
+ query parameter: `https://example.com/webhook?line=1`)
497
+
498
+ **Webhook Delivery:**
499
+
500
+ - Events are sent via HTTP POST to the target URL
501
+ - Each request includes `X-Webhook-Signature` and `X-Webhook-Timestamp` headers
502
+ - Signature is HMAC-SHA256 over `{timestamp}.{payload}` — see
503
+ [Webhook Events](/docs/webhook-events) for verification details
504
+ - Failed deliveries (5xx, 429, network errors) are retried up to 10 times over
505
+ ~25 minutes with exponential backoff
506
+ - Client errors (4xx except 429) are not retried
507
+
508
+ Args:
509
+ subscribed_events: List of event types to subscribe to
510
+
511
+ target_url: URL where webhook events will be sent. Must be HTTPS.
512
+
513
+ phone_numbers: Optional list of phone numbers to filter events for. Only events originating
514
+ from these phone numbers will be delivered to this subscription. If omitted or
515
+ empty, events from all phone numbers are delivered. Phone numbers must be in
516
+ E.164 format.
517
+
518
+ extra_headers: Send extra headers
519
+
520
+ extra_query: Add additional query parameters to the request
521
+
522
+ extra_body: Add additional JSON properties to the request
523
+
524
+ timeout: Override the client-level default timeout for this request, in seconds
525
+ """
526
+ return await self._post(
527
+ "/v3/webhook-subscriptions",
528
+ body=await async_maybe_transform(
529
+ {
530
+ "subscribed_events": subscribed_events,
531
+ "target_url": target_url,
532
+ "phone_numbers": phone_numbers,
533
+ },
534
+ webhook_subscription_create_params.WebhookSubscriptionCreateParams,
535
+ ),
536
+ options=make_request_options(
537
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
538
+ ),
539
+ cast_to=WebhookSubscriptionCreateResponse,
540
+ )
541
+
542
+ async def retrieve(
543
+ self,
544
+ subscription_id: str,
545
+ *,
546
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
547
+ # The extra values given here take precedence over values defined on the client or passed to this method.
548
+ extra_headers: Headers | None = None,
549
+ extra_query: Query | None = None,
550
+ extra_body: Body | None = None,
551
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
552
+ ) -> WebhookSubscription:
553
+ """
554
+ Retrieve details for a specific webhook subscription including its target URL,
555
+ subscribed events, and current status.
556
+
557
+ Args:
558
+ extra_headers: Send extra headers
559
+
560
+ extra_query: Add additional query parameters to the request
561
+
562
+ extra_body: Add additional JSON properties to the request
563
+
564
+ timeout: Override the client-level default timeout for this request, in seconds
565
+ """
566
+ if not subscription_id:
567
+ raise ValueError(f"Expected a non-empty value for `subscription_id` but received {subscription_id!r}")
568
+ return await self._get(
569
+ path_template("/v3/webhook-subscriptions/{subscription_id}", subscription_id=subscription_id),
570
+ options=make_request_options(
571
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
572
+ ),
573
+ cast_to=WebhookSubscription,
574
+ )
575
+
576
+ async def update(
577
+ self,
578
+ subscription_id: str,
579
+ *,
580
+ is_active: bool | Omit = omit,
581
+ phone_numbers: Optional[SequenceNotStr[str]] | Omit = omit,
582
+ subscribed_events: List[WebhookEventType] | Omit = omit,
583
+ target_url: str | Omit = omit,
584
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
585
+ # The extra values given here take precedence over values defined on the client or passed to this method.
586
+ extra_headers: Headers | None = None,
587
+ extra_query: Query | None = None,
588
+ extra_body: Body | None = None,
589
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
590
+ ) -> WebhookSubscription:
591
+ """Update an existing webhook subscription.
592
+
593
+ You can modify the target URL,
594
+ subscribed events, or activate/deactivate the subscription.
595
+
596
+ **Note:** The signing secret cannot be changed via this endpoint.
597
+
598
+ Args:
599
+ is_active: Activate or deactivate the subscription
600
+
601
+ phone_numbers: Updated list of phone numbers to filter events for. Set to a non-empty array to
602
+ filter events to specific phone numbers. Set to an empty array or null to remove
603
+ the filter and receive events from all phone numbers. Phone numbers must be in
604
+ E.164 format.
605
+
606
+ subscribed_events: Updated list of event types to subscribe to
607
+
608
+ target_url: New target URL for webhook events
609
+
610
+ extra_headers: Send extra headers
611
+
612
+ extra_query: Add additional query parameters to the request
613
+
614
+ extra_body: Add additional JSON properties to the request
615
+
616
+ timeout: Override the client-level default timeout for this request, in seconds
617
+ """
618
+ if not subscription_id:
619
+ raise ValueError(f"Expected a non-empty value for `subscription_id` but received {subscription_id!r}")
620
+ return await self._put(
621
+ path_template("/v3/webhook-subscriptions/{subscription_id}", subscription_id=subscription_id),
622
+ body=await async_maybe_transform(
623
+ {
624
+ "is_active": is_active,
625
+ "phone_numbers": phone_numbers,
626
+ "subscribed_events": subscribed_events,
627
+ "target_url": target_url,
628
+ },
629
+ webhook_subscription_update_params.WebhookSubscriptionUpdateParams,
630
+ ),
631
+ options=make_request_options(
632
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
633
+ ),
634
+ cast_to=WebhookSubscription,
635
+ )
636
+
637
+ async def list(
638
+ self,
639
+ *,
640
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
641
+ # The extra values given here take precedence over values defined on the client or passed to this method.
642
+ extra_headers: Headers | None = None,
643
+ extra_query: Query | None = None,
644
+ extra_body: Body | None = None,
645
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
646
+ ) -> WebhookSubscriptionListResponse:
647
+ """Retrieve all webhook subscriptions for the authenticated partner.
648
+
649
+ Returns a list
650
+ of active and inactive subscriptions with their configuration and status.
651
+ """
652
+ return await self._get(
653
+ "/v3/webhook-subscriptions",
654
+ options=make_request_options(
655
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
656
+ ),
657
+ cast_to=WebhookSubscriptionListResponse,
658
+ )
659
+
660
+ async def delete(
661
+ self,
662
+ subscription_id: str,
663
+ *,
664
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
665
+ # The extra values given here take precedence over values defined on the client or passed to this method.
666
+ extra_headers: Headers | None = None,
667
+ extra_query: Query | None = None,
668
+ extra_body: Body | None = None,
669
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
670
+ ) -> None:
671
+ """
672
+ Delete a webhook subscription.
673
+
674
+ Args:
675
+ extra_headers: Send extra headers
676
+
677
+ extra_query: Add additional query parameters to the request
678
+
679
+ extra_body: Add additional JSON properties to the request
680
+
681
+ timeout: Override the client-level default timeout for this request, in seconds
682
+ """
683
+ if not subscription_id:
684
+ raise ValueError(f"Expected a non-empty value for `subscription_id` but received {subscription_id!r}")
685
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
686
+ return await self._delete(
687
+ path_template("/v3/webhook-subscriptions/{subscription_id}", subscription_id=subscription_id),
688
+ options=make_request_options(
689
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
690
+ ),
691
+ cast_to=NoneType,
692
+ )
693
+
694
+
695
+ class WebhookSubscriptionsResourceWithRawResponse:
696
+ def __init__(self, webhook_subscriptions: WebhookSubscriptionsResource) -> None:
697
+ self._webhook_subscriptions = webhook_subscriptions
698
+
699
+ self.create = to_raw_response_wrapper(
700
+ webhook_subscriptions.create,
701
+ )
702
+ self.retrieve = to_raw_response_wrapper(
703
+ webhook_subscriptions.retrieve,
704
+ )
705
+ self.update = to_raw_response_wrapper(
706
+ webhook_subscriptions.update,
707
+ )
708
+ self.list = to_raw_response_wrapper(
709
+ webhook_subscriptions.list,
710
+ )
711
+ self.delete = to_raw_response_wrapper(
712
+ webhook_subscriptions.delete,
713
+ )
714
+
715
+
716
+ class AsyncWebhookSubscriptionsResourceWithRawResponse:
717
+ def __init__(self, webhook_subscriptions: AsyncWebhookSubscriptionsResource) -> None:
718
+ self._webhook_subscriptions = webhook_subscriptions
719
+
720
+ self.create = async_to_raw_response_wrapper(
721
+ webhook_subscriptions.create,
722
+ )
723
+ self.retrieve = async_to_raw_response_wrapper(
724
+ webhook_subscriptions.retrieve,
725
+ )
726
+ self.update = async_to_raw_response_wrapper(
727
+ webhook_subscriptions.update,
728
+ )
729
+ self.list = async_to_raw_response_wrapper(
730
+ webhook_subscriptions.list,
731
+ )
732
+ self.delete = async_to_raw_response_wrapper(
733
+ webhook_subscriptions.delete,
734
+ )
735
+
736
+
737
+ class WebhookSubscriptionsResourceWithStreamingResponse:
738
+ def __init__(self, webhook_subscriptions: WebhookSubscriptionsResource) -> None:
739
+ self._webhook_subscriptions = webhook_subscriptions
740
+
741
+ self.create = to_streamed_response_wrapper(
742
+ webhook_subscriptions.create,
743
+ )
744
+ self.retrieve = to_streamed_response_wrapper(
745
+ webhook_subscriptions.retrieve,
746
+ )
747
+ self.update = to_streamed_response_wrapper(
748
+ webhook_subscriptions.update,
749
+ )
750
+ self.list = to_streamed_response_wrapper(
751
+ webhook_subscriptions.list,
752
+ )
753
+ self.delete = to_streamed_response_wrapper(
754
+ webhook_subscriptions.delete,
755
+ )
756
+
757
+
758
+ class AsyncWebhookSubscriptionsResourceWithStreamingResponse:
759
+ def __init__(self, webhook_subscriptions: AsyncWebhookSubscriptionsResource) -> None:
760
+ self._webhook_subscriptions = webhook_subscriptions
761
+
762
+ self.create = async_to_streamed_response_wrapper(
763
+ webhook_subscriptions.create,
764
+ )
765
+ self.retrieve = async_to_streamed_response_wrapper(
766
+ webhook_subscriptions.retrieve,
767
+ )
768
+ self.update = async_to_streamed_response_wrapper(
769
+ webhook_subscriptions.update,
770
+ )
771
+ self.list = async_to_streamed_response_wrapper(
772
+ webhook_subscriptions.list,
773
+ )
774
+ self.delete = async_to_streamed_response_wrapper(
775
+ webhook_subscriptions.delete,
776
+ )