feat(linq): audit fixes + native auto-registering webhook trigger (#5301)

* feat(linq): audit fixes + native auto-registering webhook trigger

Tools/block audit (validated against the live Linq partner API + OpenAPI spec):
- create_chat: read the sent message from top-level response.message (was always null)
- get_message/edit_message: expose canonical deliveryStatus; mark is_delivered/is_read deprecated
- send_message: fall back to from_handle.service/preferred_service for service output
- list_phone_numbers: migrate deprecated health_status to reputation; add forwardingNumber; guard JSON parse
- check_imessage/check_rcs: guard response.json() parse
- mark_chat_read: note 1:1-only / group no-op behavior

Native webhook trigger (auto register + deregister):
- 6 triggers (message received/delivered/failed/read, reaction added, all-events)
- Standard Webhooks signature verification (HMAC-SHA256, whsec_ secret)
- createSubscription/deleteSubscription manage the Linq subscription lifecycle
- event_id idempotency; full 27-value WebhookEventType enum for all-events
- regenerated docs

* refactor(linq): drop phantom create_chat response path, complete deliveryStatus enum doc

Final validation against the raw Linq OpenAPI spec confirmed the sent message
is at chat.message (CreateChatResult exposes only chat), so the data.message
fallback was dead code. Also list all 7 DeliveryStatus values in the
send_message output description.

* fix(linq): namespace trigger credential keys, handle edit_message 204, nullable forwardingNumber

Review + final pre-merge audit fixes:
- Trigger apiKey/phoneNumbers subblocks collided with the block's tool apiKey
  state key — rename to triggerApiKey/triggerPhoneNumbers (per the namespacing
  rule from #2133) and read them in the webhook handler
- edit_message: the API returns 204 No Content when editing an already-deleted
  message; guard the empty body instead of throwing on response.json()
- list_phone_numbers: mark forwardingNumber output nullable (returns null)
- check_rcs: tighten address hint (RCS is phone-only, not email)
- regenerated docs

* fix(linq): read triggerApiKey in webhook deleteSubscription

deleteSubscription still read config.apiKey after the credential rename, so
undeploy would skip the DELETE and orphan the Linq subscription. Match
createSubscription's triggerApiKey key.

* fix(linq): mark list_phone_numbers healthStatus output nullable

healthStatus returns null when Linq omits reputation/health_status; declare
nullable: true to match the runtime value (same as forwardingNumber).

* fix(linq): mark nullable list_webhook_subscriptions item fields

phoneNumbers/createdAt/updatedAt are null-coerced by mapWebhookSubscription;
declare nullable: true on the array-item schema to match runtime (consistent
with the top-level webhook outputs' optional flags).
This commit is contained in:
Waleed
2026-06-30 17:18:57 -07:00
committed by GitHub
parent 604d03e40d
commit c1b84e4eec
23 changed files with 960 additions and 47 deletions
+157 -8
View File
@@ -88,7 +88,7 @@ Check whether an address (phone number or email) supports RCS
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Linq API key |
| `address` | string | Yes | Phone number \(E.164 format\) or email address to check |
| `address` | string | Yes | Phone number \(E.164 format\) to check |
| `from` | string | No | Sender phone number to check from \(defaults to an available number\) |
#### Output
@@ -277,8 +277,9 @@ Edit the text of a sent message (up to 5 times, within 15 minutes of sending; iM
| `id` | string | Message ID |
| `chatId` | string | ID of the chat the message belongs to |
| `isFromMe` | boolean | Whether the message was sent by you |
| `isDelivered` | boolean | Whether the message was delivered |
| `isRead` | boolean | Whether the message was read |
| `deliveryStatus` | string | Delivery status \(pending, queued, sent, delivered, received, read, failed\) |
| `isDelivered` | boolean | Whether the message was delivered \(deprecated; use deliveryStatus\) |
| `isRead` | boolean | Whether the message was read \(deprecated; use deliveryStatus\) |
| `service` | string | Delivery service \(iMessage, SMS, RCS\) |
| `createdAt` | string | ISO 8601 creation timestamp |
| `updatedAt` | string | ISO 8601 update timestamp |
@@ -374,8 +375,9 @@ Retrieve a single message by ID, including parts, reactions, and delivery status
| `id` | string | Message ID |
| `chatId` | string | ID of the chat the message belongs to |
| `isFromMe` | boolean | Whether the message was sent by you |
| `isDelivered` | boolean | Whether the message was delivered |
| `isRead` | boolean | Whether the message was read |
| `deliveryStatus` | string | Delivery status \(pending, queued, sent, delivered, received, read, failed\) |
| `isDelivered` | boolean | Whether the message was delivered \(deprecated; use deliveryStatus\) |
| `isRead` | boolean | Whether the message was read \(deprecated; use deliveryStatus\) |
| `service` | string | Delivery service \(iMessage, SMS, RCS\) |
| `createdAt` | string | ISO 8601 creation timestamp |
| `updatedAt` | string | ISO 8601 update timestamp |
@@ -483,7 +485,8 @@ List all phone numbers assigned to your partner account, with line health
| `phoneNumbers` | array | Phone numbers assigned to the account |
| ↳ `id` | string | Phone number ID |
| ↳ `phoneNumber` | string | Phone number in E.164 format |
| ↳ `healthStatus` | json | Line health status \(status, doc_url\) |
| ↳ `forwardingNumber` | string | Forwarding number in E.164 format, or null |
| ↳ `healthStatus` | json | Line reputation/health status \(status, doc_url\) |
### `linq_list_thread`
@@ -548,7 +551,7 @@ List all webhook subscriptions on your account
### `linq_mark_chat_read`
Mark all messages in a chat as read
Mark messages in a chat as read (only applies to 1:1 iMessage/RCS; no effect on group chats)
#### Input
@@ -633,7 +636,7 @@ Send a message to an existing chat, with optional media, link, effect, or reply
| --------- | ---- | ----------- |
| `chatId` | string | ID of the chat the message was sent to |
| `messageId` | string | ID of the sent message |
| `deliveryStatus` | string | Delivery status \(pending, queued, sent, delivered, failed\) |
| `deliveryStatus` | string | Delivery status \(pending, queued, sent, delivered, received, read, failed\) |
| `sentAt` | string | ISO 8601 timestamp the message was sent |
| `service` | string | Delivery service \(iMessage, SMS, RCS\) |
| `message` | json | The full sent message object with parts |
@@ -785,3 +788,149 @@ Update a webhook subscription (target URL, events, phone filter, or active state
| `updatedAt` | string | ISO 8601 update timestamp |
## Triggers
A **Trigger** is a block that starts a workflow when an event happens in this service.
### Linq Message Delivered
Trigger workflow when a message is delivered
#### Configuration
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `triggerApiKey` | string | Yes | Required to create the webhook subscription in Linq. |
| `triggerPhoneNumbers` | string | No | Comma-separated E.164 numbers to restrict which numbers deliver events. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `eventType` | string | Event type \(e.g. message.received, message.delivered, reaction.added\) |
| `eventId` | string | Unique event identifier used for deduplication |
| `createdAt` | string | ISO 8601 timestamp of when the event occurred |
| `webhookVersion` | string | Payload schema version of the delivered event |
| `data` | json | Full event payload \(shape varies by event type — message, reaction, chat, etc.\) |
---
### Linq Message Failed
Trigger workflow when a message fails to deliver
#### Configuration
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `triggerApiKey` | string | Yes | Required to create the webhook subscription in Linq. |
| `triggerPhoneNumbers` | string | No | Comma-separated E.164 numbers to restrict which numbers deliver events. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `eventType` | string | Event type \(e.g. message.received, message.delivered, reaction.added\) |
| `eventId` | string | Unique event identifier used for deduplication |
| `createdAt` | string | ISO 8601 timestamp of when the event occurred |
| `webhookVersion` | string | Payload schema version of the delivered event |
| `data` | json | Full event payload \(shape varies by event type — message, reaction, chat, etc.\) |
---
### Linq Message Read
Trigger workflow when a message is read
#### Configuration
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `triggerApiKey` | string | Yes | Required to create the webhook subscription in Linq. |
| `triggerPhoneNumbers` | string | No | Comma-separated E.164 numbers to restrict which numbers deliver events. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `eventType` | string | Event type \(e.g. message.received, message.delivered, reaction.added\) |
| `eventId` | string | Unique event identifier used for deduplication |
| `createdAt` | string | ISO 8601 timestamp of when the event occurred |
| `webhookVersion` | string | Payload schema version of the delivered event |
| `data` | json | Full event payload \(shape varies by event type — message, reaction, chat, etc.\) |
---
### Linq Message Received
Trigger workflow when an inbound message is received
#### Configuration
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `triggerApiKey` | string | Yes | Required to create the webhook subscription in Linq. |
| `triggerPhoneNumbers` | string | No | Comma-separated E.164 numbers to restrict which numbers deliver events. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `eventType` | string | Event type \(e.g. message.received, message.delivered, reaction.added\) |
| `eventId` | string | Unique event identifier used for deduplication |
| `createdAt` | string | ISO 8601 timestamp of when the event occurred |
| `webhookVersion` | string | Payload schema version of the delivered event |
| `data` | json | Full event payload \(shape varies by event type — message, reaction, chat, etc.\) |
---
### Linq Reaction Added
Trigger workflow when a reaction is added to a message
#### Configuration
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `triggerApiKey` | string | Yes | Required to create the webhook subscription in Linq. |
| `triggerPhoneNumbers` | string | No | Comma-separated E.164 numbers to restrict which numbers deliver events. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `eventType` | string | Event type \(e.g. message.received, message.delivered, reaction.added\) |
| `eventId` | string | Unique event identifier used for deduplication |
| `createdAt` | string | ISO 8601 timestamp of when the event occurred |
| `webhookVersion` | string | Payload schema version of the delivered event |
| `data` | json | Full event payload \(shape varies by event type — message, reaction, chat, etc.\) |
---
### Linq Webhook (All Events)
Trigger on any Linq webhook event (messages, reactions, chats, and more)
#### Configuration
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `triggerApiKey` | string | Yes | Required to create the webhook subscription in Linq. |
| `triggerPhoneNumbers` | string | No | Comma-separated E.164 numbers to restrict which numbers deliver events. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `eventType` | string | Event type \(e.g. message.received, message.delivered, reaction.added\) |
| `eventId` | string | Unique event identifier used for deduplication |
| `createdAt` | string | ISO 8601 timestamp of when the event occurred |
| `webhookVersion` | string | Payload schema version of the delivered event |
| `data` | json | Full event payload \(shape varies by event type — message, reaction, chat, etc.\) |