@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.
Files changed (179) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/api-promise.d.mts +1 -0
  3. package/api-promise.d.mts.map +1 -1
  4. package/api-promise.d.ts +1 -0
  5. package/api-promise.d.ts.map +1 -1
  6. package/api-promise.mjs +1 -0
  7. package/api-promise.mjs.map +1 -1
  8. package/client.d.mts +162 -84
  9. package/client.d.mts.map +1 -1
  10. package/client.d.ts +162 -84
  11. package/client.d.ts.map +1 -1
  12. package/client.js +162 -84
  13. package/client.js.map +1 -1
  14. package/client.mjs +162 -84
  15. package/client.mjs.map +1 -1
  16. package/core/api-promise.d.mts.map +1 -1
  17. package/core/api-promise.d.ts.map +1 -1
  18. package/core/api-promise.mjs.map +1 -1
  19. package/core/error.mjs.map +1 -1
  20. package/core/pagination.d.mts.map +1 -1
  21. package/core/pagination.d.ts.map +1 -1
  22. package/core/pagination.mjs.map +1 -1
  23. package/core/resource.d.mts.map +1 -1
  24. package/core/resource.d.ts.map +1 -1
  25. package/core/uploads.d.mts.map +1 -1
  26. package/core/uploads.d.ts.map +1 -1
  27. package/core/uploads.mjs.map +1 -1
  28. package/error.d.mts +1 -0
  29. package/error.d.mts.map +1 -1
  30. package/error.d.ts +1 -0
  31. package/error.d.ts.map +1 -1
  32. package/error.mjs +1 -0
  33. package/error.mjs.map +1 -1
  34. package/index.d.mts.map +1 -1
  35. package/index.d.ts.map +1 -1
  36. package/index.mjs.map +1 -1
  37. package/internal/detect-platform.mjs.map +1 -1
  38. package/internal/headers.mjs.map +1 -1
  39. package/internal/parse.d.mts.map +1 -1
  40. package/internal/parse.d.ts.map +1 -1
  41. package/internal/parse.mjs.map +1 -1
  42. package/internal/request-options.d.mts.map +1 -1
  43. package/internal/request-options.d.ts.map +1 -1
  44. package/internal/shims.d.mts +6 -0
  45. package/internal/shims.d.mts.map +1 -1
  46. package/internal/shims.d.ts +6 -0
  47. package/internal/shims.d.ts.map +1 -1
  48. package/internal/to-file.d.mts.map +1 -1
  49. package/internal/to-file.d.ts.map +1 -1
  50. package/internal/to-file.mjs.map +1 -1
  51. package/internal/types.d.mts +4 -4
  52. package/internal/types.d.mts.map +1 -1
  53. package/internal/types.d.ts +4 -4
  54. package/internal/types.d.ts.map +1 -1
  55. package/internal/uploads.d.mts.map +1 -1
  56. package/internal/uploads.d.ts.map +1 -1
  57. package/internal/uploads.mjs.map +1 -1
  58. package/internal/utils/base64.mjs.map +1 -1
  59. package/internal/utils/log.d.mts.map +1 -1
  60. package/internal/utils/log.d.ts.map +1 -1
  61. package/internal/utils/log.mjs.map +1 -1
  62. package/internal/utils/path.mjs.map +1 -1
  63. package/internal/utils/query.mjs.map +1 -1
  64. package/internal/utils/values.mjs.map +1 -1
  65. package/internal/utils.d.mts.map +1 -1
  66. package/internal/utils.d.ts.map +1 -1
  67. package/internal/utils.mjs.map +1 -1
  68. package/package.json +1 -1
  69. package/pagination.d.mts +1 -0
  70. package/pagination.d.mts.map +1 -1
  71. package/pagination.d.ts +1 -0
  72. package/pagination.d.ts.map +1 -1
  73. package/pagination.mjs +1 -0
  74. package/pagination.mjs.map +1 -1
  75. package/resource.d.mts +1 -0
  76. package/resource.d.mts.map +1 -1
  77. package/resource.d.ts +1 -0
  78. package/resource.d.ts.map +1 -1
  79. package/resource.mjs +1 -0
  80. package/resource.mjs.map +1 -1
  81. package/resources/attachments.d.mts.map +1 -1
  82. package/resources/attachments.d.ts.map +1 -1
  83. package/resources/attachments.mjs.map +1 -1
  84. package/resources/capability.d.mts.map +1 -1
  85. package/resources/capability.d.ts.map +1 -1
  86. package/resources/capability.mjs.map +1 -1
  87. package/resources/chats/chats.d.mts +4 -0
  88. package/resources/chats/chats.d.mts.map +1 -1
  89. package/resources/chats/chats.d.ts +4 -0
  90. package/resources/chats/chats.d.ts.map +1 -1
  91. package/resources/chats/chats.js +4 -0
  92. package/resources/chats/chats.js.map +1 -1
  93. package/resources/chats/chats.mjs +4 -0
  94. package/resources/chats/chats.mjs.map +1 -1
  95. package/resources/chats/index.d.mts +1 -0
  96. package/resources/chats/index.d.mts.map +1 -1
  97. package/resources/chats/index.d.ts +1 -0
  98. package/resources/chats/index.d.ts.map +1 -1
  99. package/resources/chats/index.js +3 -1
  100. package/resources/chats/index.js.map +1 -1
  101. package/resources/chats/index.mjs +1 -0
  102. package/resources/chats/index.mjs.map +1 -1
  103. package/resources/chats/location.d.mts +107 -0
  104. package/resources/chats/location.d.mts.map +1 -0
  105. package/resources/chats/location.d.ts +107 -0
  106. package/resources/chats/location.d.ts.map +1 -0
  107. package/resources/chats/location.js +68 -0
  108. package/resources/chats/location.js.map +1 -0
  109. package/resources/chats/location.mjs +64 -0
  110. package/resources/chats/location.mjs.map +1 -0
  111. package/resources/chats/messages.d.mts.map +1 -1
  112. package/resources/chats/messages.d.ts.map +1 -1
  113. package/resources/chats/messages.mjs.map +1 -1
  114. package/resources/chats/participants.d.mts.map +1 -1
  115. package/resources/chats/participants.d.ts.map +1 -1
  116. package/resources/chats/participants.mjs.map +1 -1
  117. package/resources/chats/typing.d.mts.map +1 -1
  118. package/resources/chats/typing.d.ts.map +1 -1
  119. package/resources/chats/typing.mjs.map +1 -1
  120. package/resources/chats.d.mts.map +1 -1
  121. package/resources/chats.d.ts.map +1 -1
  122. package/resources/chats.mjs.map +1 -1
  123. package/resources/contact-card.d.mts.map +1 -1
  124. package/resources/contact-card.d.ts.map +1 -1
  125. package/resources/contact-card.mjs.map +1 -1
  126. package/resources/index.d.mts.map +1 -1
  127. package/resources/index.d.ts.map +1 -1
  128. package/resources/index.mjs.map +1 -1
  129. package/resources/messages.d.mts.map +1 -1
  130. package/resources/messages.d.ts.map +1 -1
  131. package/resources/messages.mjs.map +1 -1
  132. package/resources/phone-numbers.d.mts.map +1 -1
  133. package/resources/phone-numbers.d.ts.map +1 -1
  134. package/resources/phone-numbers.mjs.map +1 -1
  135. package/resources/phonenumbers.d.mts.map +1 -1
  136. package/resources/phonenumbers.d.ts.map +1 -1
  137. package/resources/phonenumbers.mjs.map +1 -1
  138. package/resources/webhook-events.d.mts +83 -44
  139. package/resources/webhook-events.d.mts.map +1 -1
  140. package/resources/webhook-events.d.ts +83 -44
  141. package/resources/webhook-events.d.ts.map +1 -1
  142. package/resources/webhook-events.js +81 -42
  143. package/resources/webhook-events.js.map +1 -1
  144. package/resources/webhook-events.mjs +81 -42
  145. package/resources/webhook-events.mjs.map +1 -1
  146. package/resources/webhook-subscriptions.d.mts +89 -44
  147. package/resources/webhook-subscriptions.d.mts.map +1 -1
  148. package/resources/webhook-subscriptions.d.ts +89 -44
  149. package/resources/webhook-subscriptions.d.ts.map +1 -1
  150. package/resources/webhook-subscriptions.js +90 -45
  151. package/resources/webhook-subscriptions.js.map +1 -1
  152. package/resources/webhook-subscriptions.mjs +90 -45
  153. package/resources/webhook-subscriptions.mjs.map +1 -1
  154. package/resources/webhooks.d.mts +4 -4
  155. package/resources/webhooks.d.mts.map +1 -1
  156. package/resources/webhooks.d.ts +4 -4
  157. package/resources/webhooks.d.ts.map +1 -1
  158. package/resources/webhooks.mjs.map +1 -1
  159. package/resources.d.mts.map +1 -1
  160. package/resources.d.ts.map +1 -1
  161. package/resources.mjs.map +1 -1
  162. package/src/client.ts +162 -84
  163. package/src/resources/chats/chats.ts +10 -0
  164. package/src/resources/chats/index.ts +1 -0
  165. package/src/resources/chats/location.ts +138 -0
  166. package/src/resources/webhook-events.ts +85 -44
  167. package/src/resources/webhook-subscriptions.ts +89 -44
  168. package/src/resources/webhooks.ts +6 -4
  169. package/src/version.ts +1 -1
  170. package/uploads.d.mts +1 -0
  171. package/uploads.d.mts.map +1 -1
  172. package/uploads.d.ts +1 -0
  173. package/uploads.d.ts.map +1 -1
  174. package/uploads.mjs +1 -0
  175. package/uploads.mjs.map +1 -1
  176. package/version.d.mts +1 -1
  177. package/version.d.ts +1 -1
  178. package/version.js +1 -1
  179. 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
- * Each webhook request includes the following headers:
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
- * | `X-Webhook-Event` | The event type (e.g., `message.sent`, `message.received`) |
23
- * | `X-Webhook-Subscription-ID` | Your webhook subscription ID |
24
- * | `X-Webhook-Timestamp` | Unix timestamp (seconds) when the webhook was sent |
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
- * ## Verifying Webhook Signatures
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
- * All webhooks are signed using HMAC-SHA256. You should always verify the signature
30
- * to ensure the webhook originated from Linq and hasn't been tampered with.
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
- * **Signature Construction:**
46
+ * ## Signing Secrets
33
47
  *
34
- * The signature is computed over a concatenation of the timestamp and payload:
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
- * Where:
41
- * - `timestamp` is the value from the `X-Webhook-Timestamp` header
42
- * - `payload` is the raw JSON request body (exact bytes, not re-serialized)
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 `X-Webhook-Timestamp` and `X-Webhook-Signature` headers
47
- * 2. Get the raw request body bytes (do not parse and re-serialize)
48
- * 3. Concatenate: `"{timestamp}.{payload}"`
49
- * 4. Compute HMAC-SHA256 using your signing secret as the key
50
- * 5. Hex-encode the result and compare with `X-Webhook-Signature`
51
- * 6. Use constant-time comparison to prevent timing attacks
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
- * import hashlib
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
- * def verify_webhook(signing_secret, payload, timestamp, signature):
60
- * message = f"{timestamp}.{payload.decode('utf-8')}"
61
- * expected = hmac.new(
62
- * signing_secret.encode('utf-8'),
63
- * message.encode('utf-8'),
64
- * hashlib.sha256
65
- * ).hexdigest()
66
- * return hmac.compare_digest(expected, signature)
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(signingSecret, payload, timestamp, signature) {
75
- * const message = `${timestamp}.${payload}`;
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', signingSecret)
78
- * .update(message)
79
- * .digest('hex');
80
- * return crypto.timingSafeEqual(
81
- * Buffer.from(expected),
82
- * Buffer.from(signature)
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 `X-Webhook-Signature` and `X-Webhook-Timestamp` headers
114
- * - Signature is HMAC-SHA256 over `{timestamp}.{payload}` — see
115
- * [Webhook Events](/docs/webhook-events) for verification details
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":"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"}
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
- * Each webhook request includes the following headers:
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
- * | `X-Webhook-Event` | The event type (e.g., `message.sent`, `message.received`) |
23
- * | `X-Webhook-Subscription-ID` | Your webhook subscription ID |
24
- * | `X-Webhook-Timestamp` | Unix timestamp (seconds) when the webhook was sent |
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
- * ## Verifying Webhook Signatures
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
- * All webhooks are signed using HMAC-SHA256. You should always verify the signature
30
- * to ensure the webhook originated from Linq and hasn't been tampered with.
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
- * **Signature Construction:**
46
+ * ## Signing Secrets
33
47
  *
34
- * The signature is computed over a concatenation of the timestamp and payload:
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
- * Where:
41
- * - `timestamp` is the value from the `X-Webhook-Timestamp` header
42
- * - `payload` is the raw JSON request body (exact bytes, not re-serialized)
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 `X-Webhook-Timestamp` and `X-Webhook-Signature` headers
47
- * 2. Get the raw request body bytes (do not parse and re-serialize)
48
- * 3. Concatenate: `"{timestamp}.{payload}"`
49
- * 4. Compute HMAC-SHA256 using your signing secret as the key
50
- * 5. Hex-encode the result and compare with `X-Webhook-Signature`
51
- * 6. Use constant-time comparison to prevent timing attacks
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
- * import hashlib
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
- * def verify_webhook(signing_secret, payload, timestamp, signature):
60
- * message = f"{timestamp}.{payload.decode('utf-8')}"
61
- * expected = hmac.new(
62
- * signing_secret.encode('utf-8'),
63
- * message.encode('utf-8'),
64
- * hashlib.sha256
65
- * ).hexdigest()
66
- * return hmac.compare_digest(expected, signature)
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(signingSecret, payload, timestamp, signature) {
75
- * const message = `${timestamp}.${payload}`;
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', signingSecret)
78
- * .update(message)
79
- * .digest('hex');
80
- * return crypto.timingSafeEqual(
81
- * Buffer.from(expected),
82
- * Buffer.from(signature)
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 `X-Webhook-Signature` and `X-Webhook-Timestamp` headers
114
- * - Signature is HMAC-SHA256 over `{timestamp}.{payload}` — see
115
- * [Webhook Events](/docs/webhook-events) for verification details
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":"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"}
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
- * Each webhook request includes the following headers:
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
- * | `X-Webhook-Event` | The event type (e.g., `message.sent`, `message.received`) |
26
- * | `X-Webhook-Subscription-ID` | Your webhook subscription ID |
27
- * | `X-Webhook-Timestamp` | Unix timestamp (seconds) when the webhook was sent |
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
- * ## Verifying Webhook Signatures
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
- * All webhooks are signed using HMAC-SHA256. You should always verify the signature
33
- * to ensure the webhook originated from Linq and hasn't been tampered with.
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
- * **Signature Construction:**
49
+ * ## Signing Secrets
36
50
  *
37
- * The signature is computed over a concatenation of the timestamp and payload:
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
- * Where:
44
- * - `timestamp` is the value from the `X-Webhook-Timestamp` header
45
- * - `payload` is the raw JSON request body (exact bytes, not re-serialized)
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 `X-Webhook-Timestamp` and `X-Webhook-Signature` headers
50
- * 2. Get the raw request body bytes (do not parse and re-serialize)
51
- * 3. Concatenate: `"{timestamp}.{payload}"`
52
- * 4. Compute HMAC-SHA256 using your signing secret as the key
53
- * 5. Hex-encode the result and compare with `X-Webhook-Signature`
54
- * 6. Use constant-time comparison to prevent timing attacks
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
- * import hashlib
61
- *
62
- * def verify_webhook(signing_secret, payload, timestamp, signature):
63
- * message = f"{timestamp}.{payload.decode('utf-8')}"
64
- * expected = hmac.new(
65
- * signing_secret.encode('utf-8'),
66
- * message.encode('utf-8'),
67
- * hashlib.sha256
68
- * ).hexdigest()
69
- * return hmac.compare_digest(expected, signature)
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(signingSecret, payload, timestamp, signature) {
78
- * const message = `${timestamp}.${payload}`;
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', signingSecret)
81
- * .update(message)
82
- * .digest('hex');
83
- * return crypto.timingSafeEqual(
84
- * Buffer.from(expected),
85
- * Buffer.from(signature)
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 `X-Webhook-Signature` and `X-Webhook-Timestamp` headers
117
- * - Signature is HMAC-SHA256 over `{timestamp}.{payload}` — see
118
- * [Webhook Events](/docs/webhook-events) for verification details
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AACH,MAAa,oBAAqB,SAAQ,sBAAW;IACnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;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;AAtHD,oDAsHC"}
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"}