@linqapp/sdk 0.25.0 → 0.26.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/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 +162 -84
- package/client.d.mts.map +1 -1
- package/client.d.ts +162 -84
- package/client.d.ts.map +1 -1
- package/client.js +162 -84
- package/client.js.map +1 -1
- package/client.mjs +162 -84
- 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 +1 -1
- 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.map +1 -1
- 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.map +1 -1
- package/resources/index.d.ts.map +1 -1
- package/resources/index.mjs.map +1 -1
- package/resources/messages.d.mts.map +1 -1
- 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 +4 -4
- package/resources/webhooks.d.mts.map +1 -1
- package/resources/webhooks.d.ts +4 -4
- package/resources/webhooks.d.ts.map +1 -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 +162 -84
- 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/webhook-events.ts +85 -44
- package/src/resources/webhook-subscriptions.ts +89 -44
- package/src/resources/webhooks.ts +6 -4
- 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
|
@@ -15,55 +15,82 @@ import { RequestOptions } from "../internal/request-options.mjs";
|
|
|
15
15
|
*
|
|
16
16
|
* ## Webhook Headers
|
|
17
17
|
*
|
|
18
|
-
*
|
|
18
|
+
* All webhook requests include two sets of headers. **If you have an existing integration
|
|
19
|
+
* using the `X-Webhook-*` headers, nothing changes** — those headers are still sent on
|
|
20
|
+
* every delivery and work exactly as before. The new `webhook-*` headers follow the
|
|
21
|
+
* [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks) specification.
|
|
22
|
+
* You can safely ignore them if your current verification code works and you don't want to use this convention.
|
|
23
|
+
*
|
|
24
|
+
* ### Standard Webhooks Headers (Recommended)
|
|
25
|
+
*
|
|
26
|
+
* Used by [our SDK](https://github.com/linq-team/linq-node) and any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks).
|
|
19
27
|
*
|
|
20
28
|
* | Header | Description |
|
|
21
29
|
* |--------|-------------|
|
|
22
|
-
* | `
|
|
23
|
-
* | `
|
|
24
|
-
* | `
|
|
25
|
-
* | `X-Webhook-Signature` | HMAC-SHA256 signature for verification |
|
|
30
|
+
* | `webhook-id` | Unique event identifier (use as idempotency key) |
|
|
31
|
+
* | `webhook-timestamp` | Unix timestamp (seconds) when the webhook was sent |
|
|
32
|
+
* | `webhook-signature` | Standard Webhooks signature (`v1,{base64}` format) |
|
|
26
33
|
*
|
|
27
|
-
*
|
|
34
|
+
* ### Legacy Headers (Deprecated)
|
|
35
|
+
*
|
|
36
|
+
* Still sent on every delivery for backwards compatibility. Existing verification code
|
|
37
|
+
* using these headers continues to work — no changes required.
|
|
28
38
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
39
|
+
* | Header | Description |
|
|
40
|
+
* |--------|-------------|
|
|
41
|
+
* | `X-Webhook-Event` | *(deprecated)* Event type (e.g., `message.sent`) |
|
|
42
|
+
* | `X-Webhook-Subscription-ID` | *(deprecated)* Webhook subscription ID |
|
|
43
|
+
* | `X-Webhook-Timestamp` | *(deprecated)* Unix timestamp (seconds) |
|
|
44
|
+
* | `X-Webhook-Signature` | *(deprecated)* HMAC-SHA256 signature (hex-encoded) |
|
|
31
45
|
*
|
|
32
|
-
*
|
|
46
|
+
* ## Signing Secrets
|
|
33
47
|
*
|
|
34
|
-
*
|
|
48
|
+
* Signing secrets use the Standard Webhooks format: a `whsec_` prefix followed
|
|
49
|
+
* by base64-encoded random bytes (e.g., `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw7Jxx2Oll+OE=`).
|
|
35
50
|
*
|
|
36
|
-
*
|
|
37
|
-
* {timestamp}.{payload}
|
|
38
|
-
* ```
|
|
51
|
+
* Strip the `whsec_` prefix and base64-decode the remainder to get the raw key bytes.
|
|
39
52
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
53
|
+
* ## Verifying Webhook Signatures
|
|
54
|
+
*
|
|
55
|
+
* Webhooks are signed following the [Standard Webhooks specification](https://github.com/standard-webhooks/standard-webhooks).
|
|
56
|
+
* You can use any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks) to verify
|
|
57
|
+
* signatures, or implement verification manually:
|
|
58
|
+
*
|
|
59
|
+
* **Signed content:** `{webhook-id}.{webhook-timestamp}.{body}`
|
|
43
60
|
*
|
|
44
61
|
* **Verification Steps:**
|
|
45
62
|
*
|
|
46
|
-
* 1. Extract the `
|
|
47
|
-
* 2.
|
|
48
|
-
* 3.
|
|
49
|
-
* 4.
|
|
50
|
-
* 5.
|
|
51
|
-
* 6.
|
|
63
|
+
* 1. Extract the `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers
|
|
64
|
+
* 2. Reject if the timestamp is more than 5 minutes old (replay protection)
|
|
65
|
+
* 3. Get the raw request body bytes (do not parse and re-serialize)
|
|
66
|
+
* 4. Construct signed content: `"{webhook-id}.{webhook-timestamp}.{body}"`
|
|
67
|
+
* 5. Strip the `whsec_` prefix from your secret and base64-decode to get key bytes
|
|
68
|
+
* 6. Compute HMAC-SHA256 using the key bytes over the signed content
|
|
69
|
+
* 7. Base64-encode the result and compare with the value after `v1,` in `webhook-signature`
|
|
70
|
+
* 8. Use constant-time comparison to prevent timing attacks
|
|
52
71
|
*
|
|
53
72
|
* **Example (Python):**
|
|
54
73
|
*
|
|
55
74
|
* ```python
|
|
56
|
-
* import hmac
|
|
57
|
-
*
|
|
75
|
+
* import base64, hmac, hashlib
|
|
76
|
+
*
|
|
77
|
+
* def verify_webhook(secret, body, headers):
|
|
78
|
+
* msg_id = headers['webhook-id']
|
|
79
|
+
* timestamp = headers['webhook-timestamp']
|
|
80
|
+
* signature = headers['webhook-signature']
|
|
58
81
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* hashlib.sha256
|
|
65
|
-
* ).
|
|
66
|
-
*
|
|
82
|
+
* secret_str = secret.removeprefix('whsec_')
|
|
83
|
+
* key = base64.b64decode(secret_str)
|
|
84
|
+
*
|
|
85
|
+
* signed_content = f"{msg_id}.{timestamp}.{body}"
|
|
86
|
+
* expected = base64.b64encode(
|
|
87
|
+
* hmac.new(key, signed_content.encode(), hashlib.sha256).digest()
|
|
88
|
+
* ).decode()
|
|
89
|
+
*
|
|
90
|
+
* for sig in signature.split(' '):
|
|
91
|
+
* if sig.startswith('v1,') and hmac.compare_digest(expected, sig[3:]):
|
|
92
|
+
* return True
|
|
93
|
+
* return False
|
|
67
94
|
* ```
|
|
68
95
|
*
|
|
69
96
|
* **Example (Node.js):**
|
|
@@ -71,16 +98,28 @@ import { RequestOptions } from "../internal/request-options.mjs";
|
|
|
71
98
|
* ```javascript
|
|
72
99
|
* const crypto = require('crypto');
|
|
73
100
|
*
|
|
74
|
-
* function verifyWebhook(
|
|
75
|
-
* const
|
|
101
|
+
* function verifyWebhook(secret, rawBody, headers) {
|
|
102
|
+
* const msgId = headers['webhook-id'];
|
|
103
|
+
* const timestamp = headers['webhook-timestamp'];
|
|
104
|
+
* const signature = headers['webhook-signature'];
|
|
105
|
+
*
|
|
106
|
+
* const secretStr = secret.startsWith('whsec_') ? secret.slice(6) : secret;
|
|
107
|
+
* const keyBytes = Buffer.from(secretStr, 'base64');
|
|
108
|
+
* const signedContent = `${msgId}.${timestamp}.${rawBody}`;
|
|
76
109
|
* const expected = crypto
|
|
77
|
-
* .createHmac('sha256',
|
|
78
|
-
* .update(
|
|
79
|
-
* .digest('
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
110
|
+
* .createHmac('sha256', keyBytes)
|
|
111
|
+
* .update(signedContent)
|
|
112
|
+
* .digest('base64');
|
|
113
|
+
*
|
|
114
|
+
* return signature.split(' ').some(sig => {
|
|
115
|
+
* if (!sig.startsWith('v1,')) return false;
|
|
116
|
+
* try {
|
|
117
|
+
* return crypto.timingSafeEqual(
|
|
118
|
+
* Buffer.from(expected, 'base64'),
|
|
119
|
+
* Buffer.from(sig.slice(3), 'base64')
|
|
120
|
+
* );
|
|
121
|
+
* } catch { return false; }
|
|
122
|
+
* });
|
|
84
123
|
* }
|
|
85
124
|
* ```
|
|
86
125
|
*
|
|
@@ -110,9 +149,15 @@ export declare class WebhookSubscriptions extends APIResource {
|
|
|
110
149
|
* **Webhook Delivery:**
|
|
111
150
|
*
|
|
112
151
|
* - Events are sent via HTTP POST to the target URL
|
|
113
|
-
* - Each request includes
|
|
114
|
-
* -
|
|
115
|
-
*
|
|
152
|
+
* - Each request includes
|
|
153
|
+
* [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks)
|
|
154
|
+
* headers (`webhook-id`, `webhook-timestamp`, `webhook-signature`) for signature
|
|
155
|
+
* verification
|
|
156
|
+
* - Legacy `X-Webhook-*` headers are also sent for backwards compatibility
|
|
157
|
+
* (deprecated)
|
|
158
|
+
* - See
|
|
159
|
+
* [Verifying Webhook Signatures](https://docs.linqapp.com/guides/webhooks#verifying-webhook-signatures)
|
|
160
|
+
* for verification details
|
|
116
161
|
* - Failed deliveries (5xx, 429, network errors) are retried up to 10 times over
|
|
117
162
|
* ~25 minutes with exponential backoff
|
|
118
163
|
* - Client errors (4xx except 429) are not retried
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-subscriptions.d.mts","sourceRoot":"","sources":["../src/resources/webhook-subscriptions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webhook-subscriptions.d.mts","sourceRoot":"","sources":["../src/resources/webhook-subscriptions.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,55 +15,82 @@ import { RequestOptions } from "../internal/request-options.js";
|
|
|
15
15
|
*
|
|
16
16
|
* ## Webhook Headers
|
|
17
17
|
*
|
|
18
|
-
*
|
|
18
|
+
* All webhook requests include two sets of headers. **If you have an existing integration
|
|
19
|
+
* using the `X-Webhook-*` headers, nothing changes** — those headers are still sent on
|
|
20
|
+
* every delivery and work exactly as before. The new `webhook-*` headers follow the
|
|
21
|
+
* [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks) specification.
|
|
22
|
+
* You can safely ignore them if your current verification code works and you don't want to use this convention.
|
|
23
|
+
*
|
|
24
|
+
* ### Standard Webhooks Headers (Recommended)
|
|
25
|
+
*
|
|
26
|
+
* Used by [our SDK](https://github.com/linq-team/linq-node) and any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks).
|
|
19
27
|
*
|
|
20
28
|
* | Header | Description |
|
|
21
29
|
* |--------|-------------|
|
|
22
|
-
* | `
|
|
23
|
-
* | `
|
|
24
|
-
* | `
|
|
25
|
-
* | `X-Webhook-Signature` | HMAC-SHA256 signature for verification |
|
|
30
|
+
* | `webhook-id` | Unique event identifier (use as idempotency key) |
|
|
31
|
+
* | `webhook-timestamp` | Unix timestamp (seconds) when the webhook was sent |
|
|
32
|
+
* | `webhook-signature` | Standard Webhooks signature (`v1,{base64}` format) |
|
|
26
33
|
*
|
|
27
|
-
*
|
|
34
|
+
* ### Legacy Headers (Deprecated)
|
|
35
|
+
*
|
|
36
|
+
* Still sent on every delivery for backwards compatibility. Existing verification code
|
|
37
|
+
* using these headers continues to work — no changes required.
|
|
28
38
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
39
|
+
* | Header | Description |
|
|
40
|
+
* |--------|-------------|
|
|
41
|
+
* | `X-Webhook-Event` | *(deprecated)* Event type (e.g., `message.sent`) |
|
|
42
|
+
* | `X-Webhook-Subscription-ID` | *(deprecated)* Webhook subscription ID |
|
|
43
|
+
* | `X-Webhook-Timestamp` | *(deprecated)* Unix timestamp (seconds) |
|
|
44
|
+
* | `X-Webhook-Signature` | *(deprecated)* HMAC-SHA256 signature (hex-encoded) |
|
|
31
45
|
*
|
|
32
|
-
*
|
|
46
|
+
* ## Signing Secrets
|
|
33
47
|
*
|
|
34
|
-
*
|
|
48
|
+
* Signing secrets use the Standard Webhooks format: a `whsec_` prefix followed
|
|
49
|
+
* by base64-encoded random bytes (e.g., `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw7Jxx2Oll+OE=`).
|
|
35
50
|
*
|
|
36
|
-
*
|
|
37
|
-
* {timestamp}.{payload}
|
|
38
|
-
* ```
|
|
51
|
+
* Strip the `whsec_` prefix and base64-decode the remainder to get the raw key bytes.
|
|
39
52
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
53
|
+
* ## Verifying Webhook Signatures
|
|
54
|
+
*
|
|
55
|
+
* Webhooks are signed following the [Standard Webhooks specification](https://github.com/standard-webhooks/standard-webhooks).
|
|
56
|
+
* You can use any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks) to verify
|
|
57
|
+
* signatures, or implement verification manually:
|
|
58
|
+
*
|
|
59
|
+
* **Signed content:** `{webhook-id}.{webhook-timestamp}.{body}`
|
|
43
60
|
*
|
|
44
61
|
* **Verification Steps:**
|
|
45
62
|
*
|
|
46
|
-
* 1. Extract the `
|
|
47
|
-
* 2.
|
|
48
|
-
* 3.
|
|
49
|
-
* 4.
|
|
50
|
-
* 5.
|
|
51
|
-
* 6.
|
|
63
|
+
* 1. Extract the `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers
|
|
64
|
+
* 2. Reject if the timestamp is more than 5 minutes old (replay protection)
|
|
65
|
+
* 3. Get the raw request body bytes (do not parse and re-serialize)
|
|
66
|
+
* 4. Construct signed content: `"{webhook-id}.{webhook-timestamp}.{body}"`
|
|
67
|
+
* 5. Strip the `whsec_` prefix from your secret and base64-decode to get key bytes
|
|
68
|
+
* 6. Compute HMAC-SHA256 using the key bytes over the signed content
|
|
69
|
+
* 7. Base64-encode the result and compare with the value after `v1,` in `webhook-signature`
|
|
70
|
+
* 8. Use constant-time comparison to prevent timing attacks
|
|
52
71
|
*
|
|
53
72
|
* **Example (Python):**
|
|
54
73
|
*
|
|
55
74
|
* ```python
|
|
56
|
-
* import hmac
|
|
57
|
-
*
|
|
75
|
+
* import base64, hmac, hashlib
|
|
76
|
+
*
|
|
77
|
+
* def verify_webhook(secret, body, headers):
|
|
78
|
+
* msg_id = headers['webhook-id']
|
|
79
|
+
* timestamp = headers['webhook-timestamp']
|
|
80
|
+
* signature = headers['webhook-signature']
|
|
58
81
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* hashlib.sha256
|
|
65
|
-
* ).
|
|
66
|
-
*
|
|
82
|
+
* secret_str = secret.removeprefix('whsec_')
|
|
83
|
+
* key = base64.b64decode(secret_str)
|
|
84
|
+
*
|
|
85
|
+
* signed_content = f"{msg_id}.{timestamp}.{body}"
|
|
86
|
+
* expected = base64.b64encode(
|
|
87
|
+
* hmac.new(key, signed_content.encode(), hashlib.sha256).digest()
|
|
88
|
+
* ).decode()
|
|
89
|
+
*
|
|
90
|
+
* for sig in signature.split(' '):
|
|
91
|
+
* if sig.startswith('v1,') and hmac.compare_digest(expected, sig[3:]):
|
|
92
|
+
* return True
|
|
93
|
+
* return False
|
|
67
94
|
* ```
|
|
68
95
|
*
|
|
69
96
|
* **Example (Node.js):**
|
|
@@ -71,16 +98,28 @@ import { RequestOptions } from "../internal/request-options.js";
|
|
|
71
98
|
* ```javascript
|
|
72
99
|
* const crypto = require('crypto');
|
|
73
100
|
*
|
|
74
|
-
* function verifyWebhook(
|
|
75
|
-
* const
|
|
101
|
+
* function verifyWebhook(secret, rawBody, headers) {
|
|
102
|
+
* const msgId = headers['webhook-id'];
|
|
103
|
+
* const timestamp = headers['webhook-timestamp'];
|
|
104
|
+
* const signature = headers['webhook-signature'];
|
|
105
|
+
*
|
|
106
|
+
* const secretStr = secret.startsWith('whsec_') ? secret.slice(6) : secret;
|
|
107
|
+
* const keyBytes = Buffer.from(secretStr, 'base64');
|
|
108
|
+
* const signedContent = `${msgId}.${timestamp}.${rawBody}`;
|
|
76
109
|
* const expected = crypto
|
|
77
|
-
* .createHmac('sha256',
|
|
78
|
-
* .update(
|
|
79
|
-
* .digest('
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
110
|
+
* .createHmac('sha256', keyBytes)
|
|
111
|
+
* .update(signedContent)
|
|
112
|
+
* .digest('base64');
|
|
113
|
+
*
|
|
114
|
+
* return signature.split(' ').some(sig => {
|
|
115
|
+
* if (!sig.startsWith('v1,')) return false;
|
|
116
|
+
* try {
|
|
117
|
+
* return crypto.timingSafeEqual(
|
|
118
|
+
* Buffer.from(expected, 'base64'),
|
|
119
|
+
* Buffer.from(sig.slice(3), 'base64')
|
|
120
|
+
* );
|
|
121
|
+
* } catch { return false; }
|
|
122
|
+
* });
|
|
84
123
|
* }
|
|
85
124
|
* ```
|
|
86
125
|
*
|
|
@@ -110,9 +149,15 @@ export declare class WebhookSubscriptions extends APIResource {
|
|
|
110
149
|
* **Webhook Delivery:**
|
|
111
150
|
*
|
|
112
151
|
* - Events are sent via HTTP POST to the target URL
|
|
113
|
-
* - Each request includes
|
|
114
|
-
* -
|
|
115
|
-
*
|
|
152
|
+
* - Each request includes
|
|
153
|
+
* [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks)
|
|
154
|
+
* headers (`webhook-id`, `webhook-timestamp`, `webhook-signature`) for signature
|
|
155
|
+
* verification
|
|
156
|
+
* - Legacy `X-Webhook-*` headers are also sent for backwards compatibility
|
|
157
|
+
* (deprecated)
|
|
158
|
+
* - See
|
|
159
|
+
* [Verifying Webhook Signatures](https://docs.linqapp.com/guides/webhooks#verifying-webhook-signatures)
|
|
160
|
+
* for verification details
|
|
116
161
|
* - Failed deliveries (5xx, 429, network errors) are retried up to 10 times over
|
|
117
162
|
* ~25 minutes with exponential backoff
|
|
118
163
|
* - Client errors (4xx except 429) are not retried
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-subscriptions.d.ts","sourceRoot":"","sources":["../src/resources/webhook-subscriptions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webhook-subscriptions.d.ts","sourceRoot":"","sources":["../src/resources/webhook-subscriptions.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,55 +18,82 @@ const path_1 = require("../internal/utils/path.js");
|
|
|
18
18
|
*
|
|
19
19
|
* ## Webhook Headers
|
|
20
20
|
*
|
|
21
|
-
*
|
|
21
|
+
* All webhook requests include two sets of headers. **If you have an existing integration
|
|
22
|
+
* using the `X-Webhook-*` headers, nothing changes** — those headers are still sent on
|
|
23
|
+
* every delivery and work exactly as before. The new `webhook-*` headers follow the
|
|
24
|
+
* [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks) specification.
|
|
25
|
+
* You can safely ignore them if your current verification code works and you don't want to use this convention.
|
|
26
|
+
*
|
|
27
|
+
* ### Standard Webhooks Headers (Recommended)
|
|
28
|
+
*
|
|
29
|
+
* Used by [our SDK](https://github.com/linq-team/linq-node) and any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks).
|
|
22
30
|
*
|
|
23
31
|
* | Header | Description |
|
|
24
32
|
* |--------|-------------|
|
|
25
|
-
* | `
|
|
26
|
-
* | `
|
|
27
|
-
* | `
|
|
28
|
-
* | `X-Webhook-Signature` | HMAC-SHA256 signature for verification |
|
|
33
|
+
* | `webhook-id` | Unique event identifier (use as idempotency key) |
|
|
34
|
+
* | `webhook-timestamp` | Unix timestamp (seconds) when the webhook was sent |
|
|
35
|
+
* | `webhook-signature` | Standard Webhooks signature (`v1,{base64}` format) |
|
|
29
36
|
*
|
|
30
|
-
*
|
|
37
|
+
* ### Legacy Headers (Deprecated)
|
|
38
|
+
*
|
|
39
|
+
* Still sent on every delivery for backwards compatibility. Existing verification code
|
|
40
|
+
* using these headers continues to work — no changes required.
|
|
31
41
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
42
|
+
* | Header | Description |
|
|
43
|
+
* |--------|-------------|
|
|
44
|
+
* | `X-Webhook-Event` | *(deprecated)* Event type (e.g., `message.sent`) |
|
|
45
|
+
* | `X-Webhook-Subscription-ID` | *(deprecated)* Webhook subscription ID |
|
|
46
|
+
* | `X-Webhook-Timestamp` | *(deprecated)* Unix timestamp (seconds) |
|
|
47
|
+
* | `X-Webhook-Signature` | *(deprecated)* HMAC-SHA256 signature (hex-encoded) |
|
|
34
48
|
*
|
|
35
|
-
*
|
|
49
|
+
* ## Signing Secrets
|
|
36
50
|
*
|
|
37
|
-
*
|
|
51
|
+
* Signing secrets use the Standard Webhooks format: a `whsec_` prefix followed
|
|
52
|
+
* by base64-encoded random bytes (e.g., `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw7Jxx2Oll+OE=`).
|
|
38
53
|
*
|
|
39
|
-
*
|
|
40
|
-
* {timestamp}.{payload}
|
|
41
|
-
* ```
|
|
54
|
+
* Strip the `whsec_` prefix and base64-decode the remainder to get the raw key bytes.
|
|
42
55
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
56
|
+
* ## Verifying Webhook Signatures
|
|
57
|
+
*
|
|
58
|
+
* Webhooks are signed following the [Standard Webhooks specification](https://github.com/standard-webhooks/standard-webhooks).
|
|
59
|
+
* You can use any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks) to verify
|
|
60
|
+
* signatures, or implement verification manually:
|
|
61
|
+
*
|
|
62
|
+
* **Signed content:** `{webhook-id}.{webhook-timestamp}.{body}`
|
|
46
63
|
*
|
|
47
64
|
* **Verification Steps:**
|
|
48
65
|
*
|
|
49
|
-
* 1. Extract the `
|
|
50
|
-
* 2.
|
|
51
|
-
* 3.
|
|
52
|
-
* 4.
|
|
53
|
-
* 5.
|
|
54
|
-
* 6.
|
|
66
|
+
* 1. Extract the `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers
|
|
67
|
+
* 2. Reject if the timestamp is more than 5 minutes old (replay protection)
|
|
68
|
+
* 3. Get the raw request body bytes (do not parse and re-serialize)
|
|
69
|
+
* 4. Construct signed content: `"{webhook-id}.{webhook-timestamp}.{body}"`
|
|
70
|
+
* 5. Strip the `whsec_` prefix from your secret and base64-decode to get key bytes
|
|
71
|
+
* 6. Compute HMAC-SHA256 using the key bytes over the signed content
|
|
72
|
+
* 7. Base64-encode the result and compare with the value after `v1,` in `webhook-signature`
|
|
73
|
+
* 8. Use constant-time comparison to prevent timing attacks
|
|
55
74
|
*
|
|
56
75
|
* **Example (Python):**
|
|
57
76
|
*
|
|
58
77
|
* ```python
|
|
59
|
-
* import hmac
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
78
|
+
* import base64, hmac, hashlib
|
|
79
|
+
*
|
|
80
|
+
* def verify_webhook(secret, body, headers):
|
|
81
|
+
* msg_id = headers['webhook-id']
|
|
82
|
+
* timestamp = headers['webhook-timestamp']
|
|
83
|
+
* signature = headers['webhook-signature']
|
|
84
|
+
*
|
|
85
|
+
* secret_str = secret.removeprefix('whsec_')
|
|
86
|
+
* key = base64.b64decode(secret_str)
|
|
87
|
+
*
|
|
88
|
+
* signed_content = f"{msg_id}.{timestamp}.{body}"
|
|
89
|
+
* expected = base64.b64encode(
|
|
90
|
+
* hmac.new(key, signed_content.encode(), hashlib.sha256).digest()
|
|
91
|
+
* ).decode()
|
|
92
|
+
*
|
|
93
|
+
* for sig in signature.split(' '):
|
|
94
|
+
* if sig.startswith('v1,') and hmac.compare_digest(expected, sig[3:]):
|
|
95
|
+
* return True
|
|
96
|
+
* return False
|
|
70
97
|
* ```
|
|
71
98
|
*
|
|
72
99
|
* **Example (Node.js):**
|
|
@@ -74,16 +101,28 @@ const path_1 = require("../internal/utils/path.js");
|
|
|
74
101
|
* ```javascript
|
|
75
102
|
* const crypto = require('crypto');
|
|
76
103
|
*
|
|
77
|
-
* function verifyWebhook(
|
|
78
|
-
* const
|
|
104
|
+
* function verifyWebhook(secret, rawBody, headers) {
|
|
105
|
+
* const msgId = headers['webhook-id'];
|
|
106
|
+
* const timestamp = headers['webhook-timestamp'];
|
|
107
|
+
* const signature = headers['webhook-signature'];
|
|
108
|
+
*
|
|
109
|
+
* const secretStr = secret.startsWith('whsec_') ? secret.slice(6) : secret;
|
|
110
|
+
* const keyBytes = Buffer.from(secretStr, 'base64');
|
|
111
|
+
* const signedContent = `${msgId}.${timestamp}.${rawBody}`;
|
|
79
112
|
* const expected = crypto
|
|
80
|
-
* .createHmac('sha256',
|
|
81
|
-
* .update(
|
|
82
|
-
* .digest('
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
113
|
+
* .createHmac('sha256', keyBytes)
|
|
114
|
+
* .update(signedContent)
|
|
115
|
+
* .digest('base64');
|
|
116
|
+
*
|
|
117
|
+
* return signature.split(' ').some(sig => {
|
|
118
|
+
* if (!sig.startsWith('v1,')) return false;
|
|
119
|
+
* try {
|
|
120
|
+
* return crypto.timingSafeEqual(
|
|
121
|
+
* Buffer.from(expected, 'base64'),
|
|
122
|
+
* Buffer.from(sig.slice(3), 'base64')
|
|
123
|
+
* );
|
|
124
|
+
* } catch { return false; }
|
|
125
|
+
* });
|
|
87
126
|
* }
|
|
88
127
|
* ```
|
|
89
128
|
*
|
|
@@ -113,9 +152,15 @@ class WebhookSubscriptions extends resource_1.APIResource {
|
|
|
113
152
|
* **Webhook Delivery:**
|
|
114
153
|
*
|
|
115
154
|
* - Events are sent via HTTP POST to the target URL
|
|
116
|
-
* - Each request includes
|
|
117
|
-
* -
|
|
118
|
-
*
|
|
155
|
+
* - Each request includes
|
|
156
|
+
* [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks)
|
|
157
|
+
* headers (`webhook-id`, `webhook-timestamp`, `webhook-signature`) for signature
|
|
158
|
+
* verification
|
|
159
|
+
* - Legacy `X-Webhook-*` headers are also sent for backwards compatibility
|
|
160
|
+
* (deprecated)
|
|
161
|
+
* - See
|
|
162
|
+
* [Verifying Webhook Signatures](https://docs.linqapp.com/guides/webhooks#verifying-webhook-signatures)
|
|
163
|
+
* for verification details
|
|
119
164
|
* - Failed deliveries (5xx, 429, network errors) are retried up to 10 times over
|
|
120
165
|
* ~25 minutes with exponential backoff
|
|
121
166
|
* - Client errors (4xx except 429) are not retried
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-subscriptions.js","sourceRoot":"","sources":["../src/resources/webhook-subscriptions.ts"],"names":[],"mappings":";AAAA,sFAAsF;;;AAEtF,kDAA+C;AAG/C,oDAAmD;AAEnD,oDAA8C;AAE9C
|
|
1
|
+
{"version":3,"file":"webhook-subscriptions.js","sourceRoot":"","sources":["../src/resources/webhook-subscriptions.ts"],"names":[],"mappings":";AAAA,sFAAsF;;;AAEtF,kDAA+C;AAG/C,oDAAmD;AAEnD,oDAA8C;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+HG;AACH,MAAa,oBAAqB,SAAQ,sBAAW;IACnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2CG;IACH,MAAM,CACJ,IAAqC,EACrC,OAAwB;QAExB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,2BAA2B,EAAE,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IAC9E,CAAC;IAED;;;;;;;;;OASG;IACH,IAAI,CAAC,OAAwB;QAC3B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B,EAAE,OAAO,CAAC,CAAC;IAChE,CAAC;IAED;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,cAAsB,EAAE,OAAwB;QACvD,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAA,WAAI,EAAA,6BAA6B,cAAc,EAAE,EAAE,OAAO,CAAC,CAAC;IACtF,CAAC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,CACJ,cAAsB,EACtB,IAAqC,EACrC,OAAwB;QAExB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAA,WAAI,EAAA,6BAA6B,cAAc,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACnG,CAAC;IAED;;;;;;;;;OASG;IACH,MAAM,CAAC,cAAsB,EAAE,OAAwB;QACrD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAA,WAAI,EAAA,6BAA6B,cAAc,EAAE,EAAE;YAC5E,GAAG,OAAO;YACV,OAAO,EAAE,IAAA,sBAAY,EAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;SAC7D,CAAC,CAAC;IACL,CAAC;CACF;AA5HD,oDA4HC"}
|