@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
package/src/client.ts CHANGED
@@ -1093,55 +1093,82 @@ export class LinqAPIV3 {
1093
1093
  *
1094
1094
  * ## Webhook Headers
1095
1095
  *
1096
- * Each webhook request includes the following headers:
1096
+ * All webhook requests include two sets of headers. **If you have an existing integration
1097
+ * using the `X-Webhook-*` headers, nothing changes** — those headers are still sent on
1098
+ * every delivery and work exactly as before. The new `webhook-*` headers follow the
1099
+ * [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks) specification.
1100
+ * You can safely ignore them if your current verification code works and you don't want to use this convention.
1101
+ *
1102
+ * ### Standard Webhooks Headers (Recommended)
1103
+ *
1104
+ * Used by [our SDK](https://github.com/linq-team/linq-node) and any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks).
1097
1105
  *
1098
1106
  * | Header | Description |
1099
1107
  * |--------|-------------|
1100
- * | `X-Webhook-Event` | The event type (e.g., `message.sent`, `message.received`) |
1101
- * | `X-Webhook-Subscription-ID` | Your webhook subscription ID |
1102
- * | `X-Webhook-Timestamp` | Unix timestamp (seconds) when the webhook was sent |
1103
- * | `X-Webhook-Signature` | HMAC-SHA256 signature for verification |
1108
+ * | `webhook-id` | Unique event identifier (use as idempotency key) |
1109
+ * | `webhook-timestamp` | Unix timestamp (seconds) when the webhook was sent |
1110
+ * | `webhook-signature` | Standard Webhooks signature (`v1,{base64}` format) |
1104
1111
  *
1105
- * ## Verifying Webhook Signatures
1112
+ * ### Legacy Headers (Deprecated)
1113
+ *
1114
+ * Still sent on every delivery for backwards compatibility. Existing verification code
1115
+ * using these headers continues to work — no changes required.
1106
1116
  *
1107
- * All webhooks are signed using HMAC-SHA256. You should always verify the signature
1108
- * to ensure the webhook originated from Linq and hasn't been tampered with.
1117
+ * | Header | Description |
1118
+ * |--------|-------------|
1119
+ * | `X-Webhook-Event` | *(deprecated)* Event type (e.g., `message.sent`) |
1120
+ * | `X-Webhook-Subscription-ID` | *(deprecated)* Webhook subscription ID |
1121
+ * | `X-Webhook-Timestamp` | *(deprecated)* Unix timestamp (seconds) |
1122
+ * | `X-Webhook-Signature` | *(deprecated)* HMAC-SHA256 signature (hex-encoded) |
1109
1123
  *
1110
- * **Signature Construction:**
1124
+ * ## Signing Secrets
1111
1125
  *
1112
- * The signature is computed over a concatenation of the timestamp and payload:
1126
+ * Signing secrets use the Standard Webhooks format: a `whsec_` prefix followed
1127
+ * by base64-encoded random bytes (e.g., `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw7Jxx2Oll+OE=`).
1113
1128
  *
1114
- * ```
1115
- * {timestamp}.{payload}
1116
- * ```
1129
+ * Strip the `whsec_` prefix and base64-decode the remainder to get the raw key bytes.
1117
1130
  *
1118
- * Where:
1119
- * - `timestamp` is the value from the `X-Webhook-Timestamp` header
1120
- * - `payload` is the raw JSON request body (exact bytes, not re-serialized)
1131
+ * ## Verifying Webhook Signatures
1132
+ *
1133
+ * Webhooks are signed following the [Standard Webhooks specification](https://github.com/standard-webhooks/standard-webhooks).
1134
+ * You can use any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks) to verify
1135
+ * signatures, or implement verification manually:
1136
+ *
1137
+ * **Signed content:** `{webhook-id}.{webhook-timestamp}.{body}`
1121
1138
  *
1122
1139
  * **Verification Steps:**
1123
1140
  *
1124
- * 1. Extract the `X-Webhook-Timestamp` and `X-Webhook-Signature` headers
1125
- * 2. Get the raw request body bytes (do not parse and re-serialize)
1126
- * 3. Concatenate: `"{timestamp}.{payload}"`
1127
- * 4. Compute HMAC-SHA256 using your signing secret as the key
1128
- * 5. Hex-encode the result and compare with `X-Webhook-Signature`
1129
- * 6. Use constant-time comparison to prevent timing attacks
1141
+ * 1. Extract the `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers
1142
+ * 2. Reject if the timestamp is more than 5 minutes old (replay protection)
1143
+ * 3. Get the raw request body bytes (do not parse and re-serialize)
1144
+ * 4. Construct signed content: `"{webhook-id}.{webhook-timestamp}.{body}"`
1145
+ * 5. Strip the `whsec_` prefix from your secret and base64-decode to get key bytes
1146
+ * 6. Compute HMAC-SHA256 using the key bytes over the signed content
1147
+ * 7. Base64-encode the result and compare with the value after `v1,` in `webhook-signature`
1148
+ * 8. Use constant-time comparison to prevent timing attacks
1130
1149
  *
1131
1150
  * **Example (Python):**
1132
1151
  *
1133
1152
  * ```python
1134
- * import hmac
1135
- * import hashlib
1136
- *
1137
- * def verify_webhook(signing_secret, payload, timestamp, signature):
1138
- * message = f"{timestamp}.{payload.decode('utf-8')}"
1139
- * expected = hmac.new(
1140
- * signing_secret.encode('utf-8'),
1141
- * message.encode('utf-8'),
1142
- * hashlib.sha256
1143
- * ).hexdigest()
1144
- * return hmac.compare_digest(expected, signature)
1153
+ * import base64, hmac, hashlib
1154
+ *
1155
+ * def verify_webhook(secret, body, headers):
1156
+ * msg_id = headers['webhook-id']
1157
+ * timestamp = headers['webhook-timestamp']
1158
+ * signature = headers['webhook-signature']
1159
+ *
1160
+ * secret_str = secret.removeprefix('whsec_')
1161
+ * key = base64.b64decode(secret_str)
1162
+ *
1163
+ * signed_content = f"{msg_id}.{timestamp}.{body}"
1164
+ * expected = base64.b64encode(
1165
+ * hmac.new(key, signed_content.encode(), hashlib.sha256).digest()
1166
+ * ).decode()
1167
+ *
1168
+ * for sig in signature.split(' '):
1169
+ * if sig.startswith('v1,') and hmac.compare_digest(expected, sig[3:]):
1170
+ * return True
1171
+ * return False
1145
1172
  * ```
1146
1173
  *
1147
1174
  * **Example (Node.js):**
@@ -1149,16 +1176,28 @@ export class LinqAPIV3 {
1149
1176
  * ```javascript
1150
1177
  * const crypto = require('crypto');
1151
1178
  *
1152
- * function verifyWebhook(signingSecret, payload, timestamp, signature) {
1153
- * const message = `${timestamp}.${payload}`;
1179
+ * function verifyWebhook(secret, rawBody, headers) {
1180
+ * const msgId = headers['webhook-id'];
1181
+ * const timestamp = headers['webhook-timestamp'];
1182
+ * const signature = headers['webhook-signature'];
1183
+ *
1184
+ * const secretStr = secret.startsWith('whsec_') ? secret.slice(6) : secret;
1185
+ * const keyBytes = Buffer.from(secretStr, 'base64');
1186
+ * const signedContent = `${msgId}.${timestamp}.${rawBody}`;
1154
1187
  * const expected = crypto
1155
- * .createHmac('sha256', signingSecret)
1156
- * .update(message)
1157
- * .digest('hex');
1158
- * return crypto.timingSafeEqual(
1159
- * Buffer.from(expected),
1160
- * Buffer.from(signature)
1161
- * );
1188
+ * .createHmac('sha256', keyBytes)
1189
+ * .update(signedContent)
1190
+ * .digest('base64');
1191
+ *
1192
+ * return signature.split(' ').some(sig => {
1193
+ * if (!sig.startsWith('v1,')) return false;
1194
+ * try {
1195
+ * return crypto.timingSafeEqual(
1196
+ * Buffer.from(expected, 'base64'),
1197
+ * Buffer.from(sig.slice(3), 'base64')
1198
+ * );
1199
+ * } catch { return false; }
1200
+ * });
1162
1201
  * }
1163
1202
  * ```
1164
1203
  *
@@ -1184,55 +1223,82 @@ export class LinqAPIV3 {
1184
1223
  *
1185
1224
  * ## Webhook Headers
1186
1225
  *
1187
- * Each webhook request includes the following headers:
1226
+ * All webhook requests include two sets of headers. **If you have an existing integration
1227
+ * using the `X-Webhook-*` headers, nothing changes** — those headers are still sent on
1228
+ * every delivery and work exactly as before. The new `webhook-*` headers follow the
1229
+ * [Standard Webhooks](https://github.com/standard-webhooks/standard-webhooks) specification.
1230
+ * You can safely ignore them if your current verification code works and you don't want to use this convention.
1231
+ *
1232
+ * ### Standard Webhooks Headers (Recommended)
1233
+ *
1234
+ * Used by [our SDK](https://github.com/linq-team/linq-node) and any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks).
1188
1235
  *
1189
1236
  * | Header | Description |
1190
1237
  * |--------|-------------|
1191
- * | `X-Webhook-Event` | The event type (e.g., `message.sent`, `message.received`) |
1192
- * | `X-Webhook-Subscription-ID` | Your webhook subscription ID |
1193
- * | `X-Webhook-Timestamp` | Unix timestamp (seconds) when the webhook was sent |
1194
- * | `X-Webhook-Signature` | HMAC-SHA256 signature for verification |
1238
+ * | `webhook-id` | Unique event identifier (use as idempotency key) |
1239
+ * | `webhook-timestamp` | Unix timestamp (seconds) when the webhook was sent |
1240
+ * | `webhook-signature` | Standard Webhooks signature (`v1,{base64}` format) |
1195
1241
  *
1196
- * ## Verifying Webhook Signatures
1242
+ * ### Legacy Headers (Deprecated)
1243
+ *
1244
+ * Still sent on every delivery for backwards compatibility. Existing verification code
1245
+ * using these headers continues to work — no changes required.
1197
1246
  *
1198
- * All webhooks are signed using HMAC-SHA256. You should always verify the signature
1199
- * to ensure the webhook originated from Linq and hasn't been tampered with.
1247
+ * | Header | Description |
1248
+ * |--------|-------------|
1249
+ * | `X-Webhook-Event` | *(deprecated)* Event type (e.g., `message.sent`) |
1250
+ * | `X-Webhook-Subscription-ID` | *(deprecated)* Webhook subscription ID |
1251
+ * | `X-Webhook-Timestamp` | *(deprecated)* Unix timestamp (seconds) |
1252
+ * | `X-Webhook-Signature` | *(deprecated)* HMAC-SHA256 signature (hex-encoded) |
1200
1253
  *
1201
- * **Signature Construction:**
1254
+ * ## Signing Secrets
1202
1255
  *
1203
- * The signature is computed over a concatenation of the timestamp and payload:
1256
+ * Signing secrets use the Standard Webhooks format: a `whsec_` prefix followed
1257
+ * by base64-encoded random bytes (e.g., `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw7Jxx2Oll+OE=`).
1204
1258
  *
1205
- * ```
1206
- * {timestamp}.{payload}
1207
- * ```
1259
+ * Strip the `whsec_` prefix and base64-decode the remainder to get the raw key bytes.
1208
1260
  *
1209
- * Where:
1210
- * - `timestamp` is the value from the `X-Webhook-Timestamp` header
1211
- * - `payload` is the raw JSON request body (exact bytes, not re-serialized)
1261
+ * ## Verifying Webhook Signatures
1262
+ *
1263
+ * Webhooks are signed following the [Standard Webhooks specification](https://github.com/standard-webhooks/standard-webhooks).
1264
+ * You can use any [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks) to verify
1265
+ * signatures, or implement verification manually:
1266
+ *
1267
+ * **Signed content:** `{webhook-id}.{webhook-timestamp}.{body}`
1212
1268
  *
1213
1269
  * **Verification Steps:**
1214
1270
  *
1215
- * 1. Extract the `X-Webhook-Timestamp` and `X-Webhook-Signature` headers
1216
- * 2. Get the raw request body bytes (do not parse and re-serialize)
1217
- * 3. Concatenate: `"{timestamp}.{payload}"`
1218
- * 4. Compute HMAC-SHA256 using your signing secret as the key
1219
- * 5. Hex-encode the result and compare with `X-Webhook-Signature`
1220
- * 6. Use constant-time comparison to prevent timing attacks
1271
+ * 1. Extract the `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers
1272
+ * 2. Reject if the timestamp is more than 5 minutes old (replay protection)
1273
+ * 3. Get the raw request body bytes (do not parse and re-serialize)
1274
+ * 4. Construct signed content: `"{webhook-id}.{webhook-timestamp}.{body}"`
1275
+ * 5. Strip the `whsec_` prefix from your secret and base64-decode to get key bytes
1276
+ * 6. Compute HMAC-SHA256 using the key bytes over the signed content
1277
+ * 7. Base64-encode the result and compare with the value after `v1,` in `webhook-signature`
1278
+ * 8. Use constant-time comparison to prevent timing attacks
1221
1279
  *
1222
1280
  * **Example (Python):**
1223
1281
  *
1224
1282
  * ```python
1225
- * import hmac
1226
- * import hashlib
1227
- *
1228
- * def verify_webhook(signing_secret, payload, timestamp, signature):
1229
- * message = f"{timestamp}.{payload.decode('utf-8')}"
1230
- * expected = hmac.new(
1231
- * signing_secret.encode('utf-8'),
1232
- * message.encode('utf-8'),
1233
- * hashlib.sha256
1234
- * ).hexdigest()
1235
- * return hmac.compare_digest(expected, signature)
1283
+ * import base64, hmac, hashlib
1284
+ *
1285
+ * def verify_webhook(secret, body, headers):
1286
+ * msg_id = headers['webhook-id']
1287
+ * timestamp = headers['webhook-timestamp']
1288
+ * signature = headers['webhook-signature']
1289
+ *
1290
+ * secret_str = secret.removeprefix('whsec_')
1291
+ * key = base64.b64decode(secret_str)
1292
+ *
1293
+ * signed_content = f"{msg_id}.{timestamp}.{body}"
1294
+ * expected = base64.b64encode(
1295
+ * hmac.new(key, signed_content.encode(), hashlib.sha256).digest()
1296
+ * ).decode()
1297
+ *
1298
+ * for sig in signature.split(' '):
1299
+ * if sig.startswith('v1,') and hmac.compare_digest(expected, sig[3:]):
1300
+ * return True
1301
+ * return False
1236
1302
  * ```
1237
1303
  *
1238
1304
  * **Example (Node.js):**
@@ -1240,16 +1306,28 @@ export class LinqAPIV3 {
1240
1306
  * ```javascript
1241
1307
  * const crypto = require('crypto');
1242
1308
  *
1243
- * function verifyWebhook(signingSecret, payload, timestamp, signature) {
1244
- * const message = `${timestamp}.${payload}`;
1309
+ * function verifyWebhook(secret, rawBody, headers) {
1310
+ * const msgId = headers['webhook-id'];
1311
+ * const timestamp = headers['webhook-timestamp'];
1312
+ * const signature = headers['webhook-signature'];
1313
+ *
1314
+ * const secretStr = secret.startsWith('whsec_') ? secret.slice(6) : secret;
1315
+ * const keyBytes = Buffer.from(secretStr, 'base64');
1316
+ * const signedContent = `${msgId}.${timestamp}.${rawBody}`;
1245
1317
  * const expected = crypto
1246
- * .createHmac('sha256', signingSecret)
1247
- * .update(message)
1248
- * .digest('hex');
1249
- * return crypto.timingSafeEqual(
1250
- * Buffer.from(expected),
1251
- * Buffer.from(signature)
1252
- * );
1318
+ * .createHmac('sha256', keyBytes)
1319
+ * .update(signedContent)
1320
+ * .digest('base64');
1321
+ *
1322
+ * return signature.split(' ').some(sig => {
1323
+ * if (!sig.startsWith('v1,')) return false;
1324
+ * try {
1325
+ * return crypto.timingSafeEqual(
1326
+ * Buffer.from(expected, 'base64'),
1327
+ * Buffer.from(sig.slice(3), 'base64')
1328
+ * );
1329
+ * } catch { return false; }
1330
+ * });
1253
1331
  * }
1254
1332
  * ```
1255
1333
  *
@@ -3,6 +3,8 @@
3
3
  import { APIResource } from '../../core/resource';
4
4
  import * as MessagesAPI from '../messages';
5
5
  import * as Shared from '../shared';
6
+ import * as LocationAPI from './location';
7
+ import { GetChatLocationResponse, Location, LocationRequestResponse } from './location';
6
8
  import * as ChatsMessagesAPI from './messages';
7
9
  import { MessageListParams, MessageSendParams, MessageSendResponse, Messages, SentMessage } from './messages';
8
10
  import * as ParticipantsAPI from './participants';
@@ -25,6 +27,7 @@ export class Chats extends APIResource {
25
27
  participants: ParticipantsAPI.Participants = new ParticipantsAPI.Participants(this._client);
26
28
  typing: TypingAPI.Typing = new TypingAPI.Typing(this._client);
27
29
  messages: ChatsMessagesAPI.Messages = new ChatsMessagesAPI.Messages(this._client);
30
+ location: LocationAPI.Location = new LocationAPI.Location(this._client);
28
31
 
29
32
  /**
30
33
  * Create a new chat with specified participants and send an initial message. The
@@ -817,6 +820,7 @@ export interface ChatSendVoicememoParams {
817
820
  Chats.Participants = Participants;
818
821
  Chats.Typing = Typing;
819
822
  Chats.Messages = Messages;
823
+ Chats.Location = Location;
820
824
 
821
825
  export declare namespace Chats {
822
826
  export {
@@ -853,4 +857,10 @@ export declare namespace Chats {
853
857
  type MessageSendParams as MessageSendParams,
854
858
  type MessageListParams as MessageListParams,
855
859
  };
860
+
861
+ export {
862
+ Location as Location,
863
+ type GetChatLocationResponse as GetChatLocationResponse,
864
+ type LocationRequestResponse as LocationRequestResponse,
865
+ };
856
866
  }
@@ -17,6 +17,7 @@ export {
17
17
  type ChatSendVoicememoParams,
18
18
  type ChatsListChatsPagination,
19
19
  } from './chats';
20
+ export { Location, type GetChatLocationResponse, type LocationRequestResponse } from './location';
20
21
  export {
21
22
  Messages,
22
23
  type SentMessage,
@@ -0,0 +1,138 @@
1
+ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
+
3
+ import { APIResource } from '../../core/resource';
4
+ import { APIPromise } from '../../core/api-promise';
5
+ import { buildHeaders } from '../../internal/headers';
6
+ import { RequestOptions } from '../../internal/request-options';
7
+ import { path } from '../../internal/utils/path';
8
+
9
+ /**
10
+ * Request and retrieve real-time location data via iMessage.
11
+ *
12
+ * Use these endpoints to request a contact's location, retrieve location data
13
+ * for contacts who are sharing with you, and subscribe to webhooks when someone
14
+ * starts or stops sharing their location.
15
+ *
16
+ * **Coordinates** are returned in [GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946) format:
17
+ * `[longitude, latitude]` or `[longitude, latitude, altitude]` if altitude is available.
18
+ */
19
+ export class Location extends APIResource {
20
+ /**
21
+ * Send a location sharing request to a contact. They will receive an iMessage
22
+ * prompt asking them to share their location.
23
+ *
24
+ * Location requests only work in **1:1 iMessage chats** (Apple limitation).
25
+ * Attempting to request location in a group chat, or in an SMS or RCS chat,
26
+ * returns `409` (Operation not supported on this chat's service type).
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const locationRequestResponse =
31
+ * await client.chats.location.request(
32
+ * '975d0776-bd17-4273-8337-f346b4c661b0',
33
+ * );
34
+ * ```
35
+ */
36
+ request(chatID: string, options?: RequestOptions): APIPromise<LocationRequestResponse> {
37
+ return this._client.post(path`/v3/chats/${chatID}/location/request`, options);
38
+ }
39
+
40
+ /**
41
+ * Retrieve the current location for contacts sharing with you in a chat.
42
+ *
43
+ * Returns a [GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946)
44
+ * `FeatureCollection` with a `Feature` for each participant actively sharing their
45
+ * location.
46
+ *
47
+ * Works for both 1:1 and group chats. In group chats, returns a separate feature
48
+ * for each participant who is sharing. Each feature's `properties.handle`
49
+ * identifies the user.
50
+ *
51
+ * Returns an empty `features` array if no one is sharing or no location data is
52
+ * available yet.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const getChatLocationResponse =
57
+ * await client.chats.location.retrieve(
58
+ * '975d0776-bd17-4273-8337-f346b4c661b0',
59
+ * );
60
+ * ```
61
+ */
62
+ retrieve(chatID: string, options?: RequestOptions): APIPromise<GetChatLocationResponse> {
63
+ return this._client.get(path`/v3/chats/${chatID}/location`, {
64
+ ...options,
65
+ headers: buildHeaders([{ Accept: 'application/geo+json' }, options?.headers]),
66
+ });
67
+ }
68
+ }
69
+
70
+ export interface GetChatLocationResponse {
71
+ data: GetChatLocationResponse.Data;
72
+
73
+ success: boolean;
74
+ }
75
+
76
+ export namespace GetChatLocationResponse {
77
+ export interface Data {
78
+ features: Array<Data.Feature>;
79
+
80
+ type: 'FeatureCollection';
81
+ }
82
+
83
+ export namespace Data {
84
+ export interface Feature {
85
+ geometry: Feature.Geometry;
86
+
87
+ properties: Feature.Properties;
88
+
89
+ type: 'Feature';
90
+ }
91
+
92
+ export namespace Feature {
93
+ export interface Geometry {
94
+ /**
95
+ * [longitude, latitude] or [longitude, latitude, altitude]
96
+ */
97
+ coordinates: Array<number>;
98
+
99
+ type: 'Point';
100
+ }
101
+
102
+ export interface Properties {
103
+ /**
104
+ * Phone number or email of the person sharing their location
105
+ */
106
+ handle: string;
107
+
108
+ /**
109
+ * Full street address
110
+ */
111
+ address?: string;
112
+
113
+ /**
114
+ * City or locality name
115
+ */
116
+ locality?: string;
117
+
118
+ /**
119
+ * When the location was last updated
120
+ */
121
+ updated_at?: string;
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ export interface LocationRequestResponse {
128
+ message: string;
129
+
130
+ success: boolean;
131
+ }
132
+
133
+ export declare namespace Location {
134
+ export {
135
+ type GetChatLocationResponse as GetChatLocationResponse,
136
+ type LocationRequestResponse as LocationRequestResponse,
137
+ };
138
+ }