@linqapp/sdk 0.25.0 → 0.27.0
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.
- package/CHANGELOG.md +2 -2
- package/README.md +2 -2
- package/api-promise.d.mts +1 -0
- package/api-promise.d.mts.map +1 -1
- package/api-promise.d.ts +1 -0
- package/api-promise.d.ts.map +1 -1
- package/api-promise.mjs +1 -0
- package/api-promise.mjs.map +1 -1
- package/client.d.mts +176 -87
- package/client.d.mts.map +1 -1
- package/client.d.ts +176 -87
- package/client.d.ts.map +1 -1
- package/client.js +167 -85
- package/client.js.map +1 -1
- package/client.mjs +167 -85
- package/client.mjs.map +1 -1
- package/core/api-promise.d.mts.map +1 -1
- package/core/api-promise.d.ts.map +1 -1
- package/core/api-promise.mjs.map +1 -1
- package/core/error.mjs.map +1 -1
- package/core/pagination.d.mts.map +1 -1
- package/core/pagination.d.ts.map +1 -1
- package/core/pagination.mjs.map +1 -1
- package/core/resource.d.mts.map +1 -1
- package/core/resource.d.ts.map +1 -1
- package/core/uploads.d.mts.map +1 -1
- package/core/uploads.d.ts.map +1 -1
- package/core/uploads.mjs.map +1 -1
- package/error.d.mts +1 -0
- package/error.d.mts.map +1 -1
- package/error.d.ts +1 -0
- package/error.d.ts.map +1 -1
- package/error.mjs +1 -0
- package/error.mjs.map +1 -1
- package/index.d.mts.map +1 -1
- package/index.d.ts.map +1 -1
- package/index.mjs.map +1 -1
- package/internal/detect-platform.mjs.map +1 -1
- package/internal/headers.mjs.map +1 -1
- package/internal/parse.d.mts.map +1 -1
- package/internal/parse.d.ts.map +1 -1
- package/internal/parse.mjs.map +1 -1
- package/internal/request-options.d.mts.map +1 -1
- package/internal/request-options.d.ts.map +1 -1
- package/internal/shims.d.mts +6 -0
- package/internal/shims.d.mts.map +1 -1
- package/internal/shims.d.ts +6 -0
- package/internal/shims.d.ts.map +1 -1
- package/internal/to-file.d.mts.map +1 -1
- package/internal/to-file.d.ts.map +1 -1
- package/internal/to-file.mjs.map +1 -1
- package/internal/types.d.mts +4 -4
- package/internal/types.d.mts.map +1 -1
- package/internal/types.d.ts +4 -4
- package/internal/types.d.ts.map +1 -1
- package/internal/uploads.d.mts.map +1 -1
- package/internal/uploads.d.ts.map +1 -1
- package/internal/uploads.mjs.map +1 -1
- package/internal/utils/base64.mjs.map +1 -1
- package/internal/utils/log.d.mts.map +1 -1
- package/internal/utils/log.d.ts.map +1 -1
- package/internal/utils/log.mjs.map +1 -1
- package/internal/utils/path.mjs.map +1 -1
- package/internal/utils/query.mjs.map +1 -1
- package/internal/utils/values.mjs.map +1 -1
- package/internal/utils.d.mts.map +1 -1
- package/internal/utils.d.ts.map +1 -1
- package/internal/utils.mjs.map +1 -1
- package/package.json +4 -2
- package/pagination.d.mts +1 -0
- package/pagination.d.mts.map +1 -1
- package/pagination.d.ts +1 -0
- package/pagination.d.ts.map +1 -1
- package/pagination.mjs +1 -0
- package/pagination.mjs.map +1 -1
- package/resource.d.mts +1 -0
- package/resource.d.mts.map +1 -1
- package/resource.d.ts +1 -0
- package/resource.d.ts.map +1 -1
- package/resource.mjs +1 -0
- package/resource.mjs.map +1 -1
- package/resources/attachments.d.mts.map +1 -1
- package/resources/attachments.d.ts.map +1 -1
- package/resources/attachments.mjs.map +1 -1
- package/resources/capability.d.mts.map +1 -1
- package/resources/capability.d.ts.map +1 -1
- package/resources/capability.mjs.map +1 -1
- package/resources/chats/chats.d.mts +4 -0
- package/resources/chats/chats.d.mts.map +1 -1
- package/resources/chats/chats.d.ts +4 -0
- package/resources/chats/chats.d.ts.map +1 -1
- package/resources/chats/chats.js +4 -0
- package/resources/chats/chats.js.map +1 -1
- package/resources/chats/chats.mjs +4 -0
- package/resources/chats/chats.mjs.map +1 -1
- package/resources/chats/index.d.mts +1 -0
- package/resources/chats/index.d.mts.map +1 -1
- package/resources/chats/index.d.ts +1 -0
- package/resources/chats/index.d.ts.map +1 -1
- package/resources/chats/index.js +3 -1
- package/resources/chats/index.js.map +1 -1
- package/resources/chats/index.mjs +1 -0
- package/resources/chats/index.mjs.map +1 -1
- package/resources/chats/location.d.mts +107 -0
- package/resources/chats/location.d.mts.map +1 -0
- package/resources/chats/location.d.ts +107 -0
- package/resources/chats/location.d.ts.map +1 -0
- package/resources/chats/location.js +68 -0
- package/resources/chats/location.js.map +1 -0
- package/resources/chats/location.mjs +64 -0
- package/resources/chats/location.mjs.map +1 -0
- package/resources/chats/messages.d.mts +3 -2
- package/resources/chats/messages.d.mts.map +1 -1
- package/resources/chats/messages.d.ts +3 -2
- package/resources/chats/messages.d.ts.map +1 -1
- package/resources/chats/messages.mjs.map +1 -1
- package/resources/chats/participants.d.mts.map +1 -1
- package/resources/chats/participants.d.ts.map +1 -1
- package/resources/chats/participants.mjs.map +1 -1
- package/resources/chats/typing.d.mts.map +1 -1
- package/resources/chats/typing.d.ts.map +1 -1
- package/resources/chats/typing.mjs.map +1 -1
- package/resources/chats.d.mts.map +1 -1
- package/resources/chats.d.ts.map +1 -1
- package/resources/chats.mjs.map +1 -1
- package/resources/contact-card.d.mts.map +1 -1
- package/resources/contact-card.d.ts.map +1 -1
- package/resources/contact-card.mjs.map +1 -1
- package/resources/index.d.mts +1 -1
- package/resources/index.d.mts.map +1 -1
- package/resources/index.d.ts +1 -1
- package/resources/index.d.ts.map +1 -1
- package/resources/index.mjs.map +1 -1
- package/resources/messages.d.mts +9 -2
- package/resources/messages.d.mts.map +1 -1
- package/resources/messages.d.ts +9 -2
- package/resources/messages.d.ts.map +1 -1
- package/resources/messages.mjs.map +1 -1
- package/resources/phone-numbers.d.mts.map +1 -1
- package/resources/phone-numbers.d.ts.map +1 -1
- package/resources/phone-numbers.mjs.map +1 -1
- package/resources/phonenumbers.d.mts.map +1 -1
- package/resources/phonenumbers.d.ts.map +1 -1
- package/resources/phonenumbers.mjs.map +1 -1
- package/resources/webhook-events.d.mts +83 -44
- package/resources/webhook-events.d.mts.map +1 -1
- package/resources/webhook-events.d.ts +83 -44
- package/resources/webhook-events.d.ts.map +1 -1
- package/resources/webhook-events.js +81 -42
- package/resources/webhook-events.js.map +1 -1
- package/resources/webhook-events.mjs +81 -42
- package/resources/webhook-events.mjs.map +1 -1
- package/resources/webhook-subscriptions.d.mts +89 -44
- package/resources/webhook-subscriptions.d.mts.map +1 -1
- package/resources/webhook-subscriptions.d.ts +89 -44
- package/resources/webhook-subscriptions.d.ts.map +1 -1
- package/resources/webhook-subscriptions.js +90 -45
- package/resources/webhook-subscriptions.js.map +1 -1
- package/resources/webhook-subscriptions.mjs +90 -45
- package/resources/webhook-subscriptions.mjs.map +1 -1
- package/resources/webhooks.d.mts +10 -7
- package/resources/webhooks.d.mts.map +1 -1
- package/resources/webhooks.d.ts +10 -7
- package/resources/webhooks.d.ts.map +1 -1
- package/resources/webhooks.js +9 -1
- package/resources/webhooks.js.map +1 -1
- package/resources/webhooks.mjs +9 -1
- package/resources/webhooks.mjs.map +1 -1
- package/resources.d.mts.map +1 -1
- package/resources.d.ts.map +1 -1
- package/resources.mjs.map +1 -1
- package/src/client.ts +180 -86
- package/src/resources/chats/chats.ts +10 -0
- package/src/resources/chats/index.ts +1 -0
- package/src/resources/chats/location.ts +138 -0
- package/src/resources/chats/messages.ts +3 -2
- package/src/resources/index.ts +1 -1
- package/src/resources/messages.ts +10 -2
- package/src/resources/webhook-events.ts +85 -44
- package/src/resources/webhook-subscriptions.ts +89 -44
- package/src/resources/webhooks.ts +20 -8
- package/src/version.ts +1 -1
- package/uploads.d.mts +1 -0
- package/uploads.d.mts.map +1 -1
- package/uploads.d.ts +1 -0
- package/uploads.d.ts.map +1 -1
- package/uploads.mjs +1 -0
- package/uploads.mjs.map +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
|
@@ -17,55 +17,82 @@ import { RequestOptions } from '../internal/request-options';
|
|
|
17
17
|
*
|
|
18
18
|
* ## Webhook Headers
|
|
19
19
|
*
|
|
20
|
-
*
|
|
20
|
+
* All webhook requests include two sets of headers. **If you have an existing integration
|
|
21
|
+
* using the `X-Webhook-*` headers, nothing changes** — those headers are still sent on
|
|
22
|
+
* every delivery and work exactly as before. The new `webhook-*` headers follow the
|
|
23
|
+
* [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks) specification.
|
|
24
|
+
* You can safely ignore them if your current verification code works and you don't want to use this convention.
|
|
25
|
+
*
|
|
26
|
+
* ### Standard Webhooks Headers (Recommended)
|
|
27
|
+
*
|
|
28
|
+
* Used by [our SDK](https://github.com/linq-team/linq-node) and any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks).
|
|
21
29
|
*
|
|
22
30
|
* | Header | Description |
|
|
23
31
|
* |--------|-------------|
|
|
24
|
-
* | `
|
|
25
|
-
* | `
|
|
26
|
-
* | `
|
|
27
|
-
* | `X-Webhook-Signature` | HMAC-SHA256 signature for verification |
|
|
32
|
+
* | `webhook-id` | Unique event identifier (use as idempotency key) |
|
|
33
|
+
* | `webhook-timestamp` | Unix timestamp (seconds) when the webhook was sent |
|
|
34
|
+
* | `webhook-signature` | Standard Webhooks signature (`v1,{base64}` format) |
|
|
28
35
|
*
|
|
29
|
-
*
|
|
36
|
+
* ### Legacy Headers (Deprecated)
|
|
37
|
+
*
|
|
38
|
+
* Still sent on every delivery for backwards compatibility. Existing verification code
|
|
39
|
+
* using these headers continues to work — no changes required.
|
|
30
40
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
41
|
+
* | Header | Description |
|
|
42
|
+
* |--------|-------------|
|
|
43
|
+
* | `X-Webhook-Event` | *(deprecated)* Event type (e.g., `message.sent`) |
|
|
44
|
+
* | `X-Webhook-Subscription-ID` | *(deprecated)* Webhook subscription ID |
|
|
45
|
+
* | `X-Webhook-Timestamp` | *(deprecated)* Unix timestamp (seconds) |
|
|
46
|
+
* | `X-Webhook-Signature` | *(deprecated)* HMAC-SHA256 signature (hex-encoded) |
|
|
33
47
|
*
|
|
34
|
-
*
|
|
48
|
+
* ## Signing Secrets
|
|
35
49
|
*
|
|
36
|
-
*
|
|
50
|
+
* Signing secrets use the Standard Webhooks format: a `whsec_` prefix followed
|
|
51
|
+
* by base64-encoded random bytes (e.g., `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw7Jxx2Oll+OE=`).
|
|
37
52
|
*
|
|
38
|
-
*
|
|
39
|
-
* {timestamp}.{payload}
|
|
40
|
-
* ```
|
|
53
|
+
* Strip the `whsec_` prefix and base64-decode the remainder to get the raw key bytes.
|
|
41
54
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
55
|
+
* ## Verifying Webhook Signatures
|
|
56
|
+
*
|
|
57
|
+
* Webhooks are signed following the [Standard Webhooks specification](https://github.com/standard-webhooks/standard-webhooks).
|
|
58
|
+
* You can use any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks) to verify
|
|
59
|
+
* signatures, or implement verification manually:
|
|
60
|
+
*
|
|
61
|
+
* **Signed content:** `{webhook-id}.{webhook-timestamp}.{body}`
|
|
45
62
|
*
|
|
46
63
|
* **Verification Steps:**
|
|
47
64
|
*
|
|
48
|
-
* 1. Extract the `
|
|
49
|
-
* 2.
|
|
50
|
-
* 3.
|
|
51
|
-
* 4.
|
|
52
|
-
* 5.
|
|
53
|
-
* 6.
|
|
65
|
+
* 1. Extract the `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers
|
|
66
|
+
* 2. Reject if the timestamp is more than 5 minutes old (replay protection)
|
|
67
|
+
* 3. Get the raw request body bytes (do not parse and re-serialize)
|
|
68
|
+
* 4. Construct signed content: `"{webhook-id}.{webhook-timestamp}.{body}"`
|
|
69
|
+
* 5. Strip the `whsec_` prefix from your secret and base64-decode to get key bytes
|
|
70
|
+
* 6. Compute HMAC-SHA256 using the key bytes over the signed content
|
|
71
|
+
* 7. Base64-encode the result and compare with the value after `v1,` in `webhook-signature`
|
|
72
|
+
* 8. Use constant-time comparison to prevent timing attacks
|
|
54
73
|
*
|
|
55
74
|
* **Example (Python):**
|
|
56
75
|
*
|
|
57
76
|
* ```python
|
|
58
|
-
* import hmac
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
77
|
+
* import base64, hmac, hashlib
|
|
78
|
+
*
|
|
79
|
+
* def verify_webhook(secret, body, headers):
|
|
80
|
+
* msg_id = headers['webhook-id']
|
|
81
|
+
* timestamp = headers['webhook-timestamp']
|
|
82
|
+
* signature = headers['webhook-signature']
|
|
83
|
+
*
|
|
84
|
+
* secret_str = secret.removeprefix('whsec_')
|
|
85
|
+
* key = base64.b64decode(secret_str)
|
|
86
|
+
*
|
|
87
|
+
* signed_content = f"{msg_id}.{timestamp}.{body}"
|
|
88
|
+
* expected = base64.b64encode(
|
|
89
|
+
* hmac.new(key, signed_content.encode(), hashlib.sha256).digest()
|
|
90
|
+
* ).decode()
|
|
91
|
+
*
|
|
92
|
+
* for sig in signature.split(' '):
|
|
93
|
+
* if sig.startswith('v1,') and hmac.compare_digest(expected, sig[3:]):
|
|
94
|
+
* return True
|
|
95
|
+
* return False
|
|
69
96
|
* ```
|
|
70
97
|
*
|
|
71
98
|
* **Example (Node.js):**
|
|
@@ -73,16 +100,28 @@ import { RequestOptions } from '../internal/request-options';
|
|
|
73
100
|
* ```javascript
|
|
74
101
|
* const crypto = require('crypto');
|
|
75
102
|
*
|
|
76
|
-
* function verifyWebhook(
|
|
77
|
-
* const
|
|
103
|
+
* function verifyWebhook(secret, rawBody, headers) {
|
|
104
|
+
* const msgId = headers['webhook-id'];
|
|
105
|
+
* const timestamp = headers['webhook-timestamp'];
|
|
106
|
+
* const signature = headers['webhook-signature'];
|
|
107
|
+
*
|
|
108
|
+
* const secretStr = secret.startsWith('whsec_') ? secret.slice(6) : secret;
|
|
109
|
+
* const keyBytes = Buffer.from(secretStr, 'base64');
|
|
110
|
+
* const signedContent = `${msgId}.${timestamp}.${rawBody}`;
|
|
78
111
|
* const expected = crypto
|
|
79
|
-
* .createHmac('sha256',
|
|
80
|
-
* .update(
|
|
81
|
-
* .digest('
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
112
|
+
* .createHmac('sha256', keyBytes)
|
|
113
|
+
* .update(signedContent)
|
|
114
|
+
* .digest('base64');
|
|
115
|
+
*
|
|
116
|
+
* return signature.split(' ').some(sig => {
|
|
117
|
+
* if (!sig.startsWith('v1,')) return false;
|
|
118
|
+
* try {
|
|
119
|
+
* return crypto.timingSafeEqual(
|
|
120
|
+
* Buffer.from(expected, 'base64'),
|
|
121
|
+
* Buffer.from(sig.slice(3), 'base64')
|
|
122
|
+
* );
|
|
123
|
+
* } catch { return false; }
|
|
124
|
+
* });
|
|
86
125
|
* }
|
|
87
126
|
* ```
|
|
88
127
|
*
|
|
@@ -136,13 +175,15 @@ export type WebhookEventType =
|
|
|
136
175
|
| 'call.ended'
|
|
137
176
|
| 'call.failed'
|
|
138
177
|
| 'call.declined'
|
|
139
|
-
| 'call.no_answer'
|
|
178
|
+
| 'call.no_answer'
|
|
179
|
+
| 'location.sharing.started'
|
|
180
|
+
| 'location.sharing.stopped';
|
|
140
181
|
|
|
141
182
|
export interface WebhookEventListResponse {
|
|
142
183
|
/**
|
|
143
184
|
* URL to the webhook events documentation
|
|
144
185
|
*/
|
|
145
|
-
doc_url: 'https://
|
|
186
|
+
doc_url: 'https://docs.linqapp.com/guides/webhooks/events';
|
|
146
187
|
|
|
147
188
|
/**
|
|
148
189
|
* List of all available webhook event types
|
|
@@ -20,55 +20,82 @@ import { path } from '../internal/utils/path';
|
|
|
20
20
|
*
|
|
21
21
|
* ## Webhook Headers
|
|
22
22
|
*
|
|
23
|
-
*
|
|
23
|
+
* All webhook requests include two sets of headers. **If you have an existing integration
|
|
24
|
+
* using the `X-Webhook-*` headers, nothing changes** — those headers are still sent on
|
|
25
|
+
* every delivery and work exactly as before. The new `webhook-*` headers follow the
|
|
26
|
+
* [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks) specification.
|
|
27
|
+
* You can safely ignore them if your current verification code works and you don't want to use this convention.
|
|
28
|
+
*
|
|
29
|
+
* ### Standard Webhooks Headers (Recommended)
|
|
30
|
+
*
|
|
31
|
+
* Used by [our SDK](https://github.com/linq-team/linq-node) and any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks).
|
|
24
32
|
*
|
|
25
33
|
* | Header | Description |
|
|
26
34
|
* |--------|-------------|
|
|
27
|
-
* | `
|
|
28
|
-
* | `
|
|
29
|
-
* | `
|
|
30
|
-
* | `X-Webhook-Signature` | HMAC-SHA256 signature for verification |
|
|
35
|
+
* | `webhook-id` | Unique event identifier (use as idempotency key) |
|
|
36
|
+
* | `webhook-timestamp` | Unix timestamp (seconds) when the webhook was sent |
|
|
37
|
+
* | `webhook-signature` | Standard Webhooks signature (`v1,{base64}` format) |
|
|
31
38
|
*
|
|
32
|
-
*
|
|
39
|
+
* ### Legacy Headers (Deprecated)
|
|
40
|
+
*
|
|
41
|
+
* Still sent on every delivery for backwards compatibility. Existing verification code
|
|
42
|
+
* using these headers continues to work — no changes required.
|
|
33
43
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
44
|
+
* | Header | Description |
|
|
45
|
+
* |--------|-------------|
|
|
46
|
+
* | `X-Webhook-Event` | *(deprecated)* Event type (e.g., `message.sent`) |
|
|
47
|
+
* | `X-Webhook-Subscription-ID` | *(deprecated)* Webhook subscription ID |
|
|
48
|
+
* | `X-Webhook-Timestamp` | *(deprecated)* Unix timestamp (seconds) |
|
|
49
|
+
* | `X-Webhook-Signature` | *(deprecated)* HMAC-SHA256 signature (hex-encoded) |
|
|
36
50
|
*
|
|
37
|
-
*
|
|
51
|
+
* ## Signing Secrets
|
|
38
52
|
*
|
|
39
|
-
*
|
|
53
|
+
* Signing secrets use the Standard Webhooks format: a `whsec_` prefix followed
|
|
54
|
+
* by base64-encoded random bytes (e.g., `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw7Jxx2Oll+OE=`).
|
|
40
55
|
*
|
|
41
|
-
*
|
|
42
|
-
* {timestamp}.{payload}
|
|
43
|
-
* ```
|
|
56
|
+
* Strip the `whsec_` prefix and base64-decode the remainder to get the raw key bytes.
|
|
44
57
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
58
|
+
* ## Verifying Webhook Signatures
|
|
59
|
+
*
|
|
60
|
+
* Webhooks are signed following the [Standard Webhooks specification](https://github.com/standard-webhooks/standard-webhooks).
|
|
61
|
+
* You can use any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks) to verify
|
|
62
|
+
* signatures, or implement verification manually:
|
|
63
|
+
*
|
|
64
|
+
* **Signed content:** `{webhook-id}.{webhook-timestamp}.{body}`
|
|
48
65
|
*
|
|
49
66
|
* **Verification Steps:**
|
|
50
67
|
*
|
|
51
|
-
* 1. Extract the `
|
|
52
|
-
* 2.
|
|
53
|
-
* 3.
|
|
54
|
-
* 4.
|
|
55
|
-
* 5.
|
|
56
|
-
* 6.
|
|
68
|
+
* 1. Extract the `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers
|
|
69
|
+
* 2. Reject if the timestamp is more than 5 minutes old (replay protection)
|
|
70
|
+
* 3. Get the raw request body bytes (do not parse and re-serialize)
|
|
71
|
+
* 4. Construct signed content: `"{webhook-id}.{webhook-timestamp}.{body}"`
|
|
72
|
+
* 5. Strip the `whsec_` prefix from your secret and base64-decode to get key bytes
|
|
73
|
+
* 6. Compute HMAC-SHA256 using the key bytes over the signed content
|
|
74
|
+
* 7. Base64-encode the result and compare with the value after `v1,` in `webhook-signature`
|
|
75
|
+
* 8. Use constant-time comparison to prevent timing attacks
|
|
57
76
|
*
|
|
58
77
|
* **Example (Python):**
|
|
59
78
|
*
|
|
60
79
|
* ```python
|
|
61
|
-
* import hmac
|
|
62
|
-
*
|
|
80
|
+
* import base64, hmac, hashlib
|
|
81
|
+
*
|
|
82
|
+
* def verify_webhook(secret, body, headers):
|
|
83
|
+
* msg_id = headers['webhook-id']
|
|
84
|
+
* timestamp = headers['webhook-timestamp']
|
|
85
|
+
* signature = headers['webhook-signature']
|
|
63
86
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* hashlib.sha256
|
|
70
|
-
* ).
|
|
71
|
-
*
|
|
87
|
+
* secret_str = secret.removeprefix('whsec_')
|
|
88
|
+
* key = base64.b64decode(secret_str)
|
|
89
|
+
*
|
|
90
|
+
* signed_content = f"{msg_id}.{timestamp}.{body}"
|
|
91
|
+
* expected = base64.b64encode(
|
|
92
|
+
* hmac.new(key, signed_content.encode(), hashlib.sha256).digest()
|
|
93
|
+
* ).decode()
|
|
94
|
+
*
|
|
95
|
+
* for sig in signature.split(' '):
|
|
96
|
+
* if sig.startswith('v1,') and hmac.compare_digest(expected, sig[3:]):
|
|
97
|
+
* return True
|
|
98
|
+
* return False
|
|
72
99
|
* ```
|
|
73
100
|
*
|
|
74
101
|
* **Example (Node.js):**
|
|
@@ -76,16 +103,28 @@ import { path } from '../internal/utils/path';
|
|
|
76
103
|
* ```javascript
|
|
77
104
|
* const crypto = require('crypto');
|
|
78
105
|
*
|
|
79
|
-
* function verifyWebhook(
|
|
80
|
-
* const
|
|
106
|
+
* function verifyWebhook(secret, rawBody, headers) {
|
|
107
|
+
* const msgId = headers['webhook-id'];
|
|
108
|
+
* const timestamp = headers['webhook-timestamp'];
|
|
109
|
+
* const signature = headers['webhook-signature'];
|
|
110
|
+
*
|
|
111
|
+
* const secretStr = secret.startsWith('whsec_') ? secret.slice(6) : secret;
|
|
112
|
+
* const keyBytes = Buffer.from(secretStr, 'base64');
|
|
113
|
+
* const signedContent = `${msgId}.${timestamp}.${rawBody}`;
|
|
81
114
|
* const expected = crypto
|
|
82
|
-
* .createHmac('sha256',
|
|
83
|
-
* .update(
|
|
84
|
-
* .digest('
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
115
|
+
* .createHmac('sha256', keyBytes)
|
|
116
|
+
* .update(signedContent)
|
|
117
|
+
* .digest('base64');
|
|
118
|
+
*
|
|
119
|
+
* return signature.split(' ').some(sig => {
|
|
120
|
+
* if (!sig.startsWith('v1,')) return false;
|
|
121
|
+
* try {
|
|
122
|
+
* return crypto.timingSafeEqual(
|
|
123
|
+
* Buffer.from(expected, 'base64'),
|
|
124
|
+
* Buffer.from(sig.slice(3), 'base64')
|
|
125
|
+
* );
|
|
126
|
+
* } catch { return false; }
|
|
127
|
+
* });
|
|
89
128
|
* }
|
|
90
129
|
* ```
|
|
91
130
|
*
|
|
@@ -115,9 +154,15 @@ export class WebhookSubscriptions extends APIResource {
|
|
|
115
154
|
* **Webhook Delivery:**
|
|
116
155
|
*
|
|
117
156
|
* - Events are sent via HTTP POST to the target URL
|
|
118
|
-
* - Each request includes
|
|
119
|
-
* -
|
|
120
|
-
*
|
|
157
|
+
* - Each request includes
|
|
158
|
+
* [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks)
|
|
159
|
+
* headers (`webhook-id`, `webhook-timestamp`, `webhook-signature`) for signature
|
|
160
|
+
* verification
|
|
161
|
+
* - Legacy `X-Webhook-*` headers are also sent for backwards compatibility
|
|
162
|
+
* (deprecated)
|
|
163
|
+
* - See
|
|
164
|
+
* [Verifying Webhook Signatures](https://docs.linqapp.com/guides/webhooks#verifying-webhook-signatures)
|
|
165
|
+
* for verification details
|
|
121
166
|
* - Failed deliveries (5xx, 429, network errors) are retried up to 10 times over
|
|
122
167
|
* ~25 minutes with exponential backoff
|
|
123
168
|
* - Client errors (4xx except 429) are not retried
|
|
@@ -3,10 +3,20 @@
|
|
|
3
3
|
import { APIResource } from '../core/resource';
|
|
4
4
|
import * as Shared from './shared';
|
|
5
5
|
import * as WebhookEventsAPI from './webhook-events';
|
|
6
|
+
import { Webhook } from 'standardwebhooks';
|
|
6
7
|
|
|
7
8
|
export class Webhooks extends APIResource {
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
unwrap(
|
|
10
|
+
body: string,
|
|
11
|
+
{ headers, key }: { headers: Record<string, string>; key?: string },
|
|
12
|
+
): UnwrapWebhookEvent {
|
|
13
|
+
if (headers !== undefined) {
|
|
14
|
+
const keyStr: string | null = key === undefined ? this._client.webhookSecret : key;
|
|
15
|
+
if (keyStr === null) throw new Error('Webhook key must not be null in order to unwrap');
|
|
16
|
+
const wh = new Webhook(keyStr);
|
|
17
|
+
wh.verify(body, headers);
|
|
18
|
+
}
|
|
19
|
+
return JSON.parse(body) as UnwrapWebhookEvent;
|
|
10
20
|
}
|
|
11
21
|
}
|
|
12
22
|
|
|
@@ -794,7 +804,7 @@ export namespace MessageFailedWebhookEvent {
|
|
|
794
804
|
*/
|
|
795
805
|
export interface Data {
|
|
796
806
|
/**
|
|
797
|
-
* Error codes in webhook failure events (3007, 4001).
|
|
807
|
+
* Error codes in webhook failure events (3007, 4001, 4005).
|
|
798
808
|
*/
|
|
799
809
|
code: number;
|
|
800
810
|
|
|
@@ -1638,7 +1648,7 @@ export namespace ChatGroupNameUpdateFailedWebhookEvent {
|
|
|
1638
1648
|
chat_id: string;
|
|
1639
1649
|
|
|
1640
1650
|
/**
|
|
1641
|
-
* Error codes in webhook failure events (3007, 4001).
|
|
1651
|
+
* Error codes in webhook failure events (3007, 4001, 4005).
|
|
1642
1652
|
*/
|
|
1643
1653
|
error_code: number;
|
|
1644
1654
|
|
|
@@ -1715,7 +1725,7 @@ export namespace ChatGroupIconUpdateFailedWebhookEvent {
|
|
|
1715
1725
|
chat_id: string;
|
|
1716
1726
|
|
|
1717
1727
|
/**
|
|
1718
|
-
* Error codes in webhook failure events (3007, 4001).
|
|
1728
|
+
* Error codes in webhook failure events (3007, 4001, 4005).
|
|
1719
1729
|
*/
|
|
1720
1730
|
error_code: number;
|
|
1721
1731
|
|
|
@@ -1904,7 +1914,9 @@ export interface PhoneNumberStatusUpdatedWebhookEvent {
|
|
|
1904
1914
|
| 'call.ended'
|
|
1905
1915
|
| 'call.failed'
|
|
1906
1916
|
| 'call.declined'
|
|
1907
|
-
| 'call.no_answer'
|
|
1917
|
+
| 'call.no_answer'
|
|
1918
|
+
| 'location.sharing.started'
|
|
1919
|
+
| 'location.sharing.stopped';
|
|
1908
1920
|
|
|
1909
1921
|
/**
|
|
1910
1922
|
* Partner identifier. Present on all webhooks for cross-referencing.
|
|
@@ -1964,7 +1976,7 @@ export namespace PhoneNumberStatusUpdatedWebhookEvent {
|
|
|
1964
1976
|
/**
|
|
1965
1977
|
* Complete webhook payload for message.sent events (2026-02-03 format)
|
|
1966
1978
|
*/
|
|
1967
|
-
export type
|
|
1979
|
+
export type UnwrapWebhookEvent =
|
|
1968
1980
|
| MessageSentWebhookEvent
|
|
1969
1981
|
| MessageReceivedWebhookEvent
|
|
1970
1982
|
| MessageReadWebhookEvent
|
|
@@ -2010,6 +2022,6 @@ export declare namespace Webhooks {
|
|
|
2010
2022
|
type ChatTypingIndicatorStartedWebhookEvent as ChatTypingIndicatorStartedWebhookEvent,
|
|
2011
2023
|
type ChatTypingIndicatorStoppedWebhookEvent as ChatTypingIndicatorStoppedWebhookEvent,
|
|
2012
2024
|
type PhoneNumberStatusUpdatedWebhookEvent as PhoneNumberStatusUpdatedWebhookEvent,
|
|
2013
|
-
type
|
|
2025
|
+
type UnwrapWebhookEvent as UnwrapWebhookEvent,
|
|
2014
2026
|
};
|
|
2015
2027
|
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.27.0'; // x-release-please-version
|
package/uploads.d.mts
CHANGED
package/uploads.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploads.d.mts","sourceRoot":"","sources":["src/uploads.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"uploads.d.mts","sourceRoot":"","sources":["src/uploads.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,mCAA+B"}
|
package/uploads.d.ts
CHANGED
package/uploads.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploads.d.ts","sourceRoot":"","sources":["src/uploads.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"uploads.d.ts","sourceRoot":"","sources":["src/uploads.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,kCAA+B"}
|
package/uploads.mjs
CHANGED
package/uploads.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploads.mjs","sourceRoot":"","sources":["src/uploads.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"uploads.mjs","sourceRoot":"","sources":["src/uploads.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,mCAA+B"}
|
package/version.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.27.0";
|
|
2
2
|
//# sourceMappingURL=version.d.mts.map
|
package/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.27.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/version.js
CHANGED
package/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.27.0'; // x-release-please-version
|
|
2
2
|
//# sourceMappingURL=version.mjs.map
|