diff --git a/src/tests/verify-plain-webhook.test.ts b/src/tests/verify-plain-webhook.test.ts index 21ed065..746e2b8 100644 --- a/src/tests/verify-plain-webhook.test.ts +++ b/src/tests/verify-plain-webhook.test.ts @@ -50,7 +50,7 @@ describe('verifyPlainWebhook', () => { it('returns an error when the signature matches but the timestamp is too far in the past', () => { const result = verifyPlainWebhook( JSON.stringify(threadCreatedPayload), - '3d8627428b7dd12c590c963d3522b07800de718a08704a52cd45e20a14f95f75', + 'ae0d54c966dea1056b6ced04d1bafc3358af592469b5fd55553dcb036b58a1ef', 'secret' ); @@ -66,7 +66,7 @@ describe('verifyPlainWebhook', () => { const result = verifyPlainWebhook( JSON.stringify(threadCreatedPayload), - '3d8627428b7dd12c590c963d3522b07800de718a08704a52cd45e20a14f95f75', + 'ae0d54c966dea1056b6ced04d1bafc3358af592469b5fd55553dcb036b58a1ef', 'secret' ); @@ -99,7 +99,7 @@ describe('verifyPlainWebhook', () => { const result = verifyPlainWebhook( JSON.stringify(invalidPayload), - '1c526c694a3e2f25ebfec5daa642101e2ce9618a84a1515d04f85b5e714910d8', + '5656d05d265cea83d60afe94b440eff2ef403a8d77f6bf5483e5e83f3e5b1499', 'secret' ); diff --git a/src/tests/webhook-payloads/customer-created.ts b/src/tests/webhook-payloads/customer-created.ts index ec99d68..224f64d 100644 --- a/src/tests/webhook-payloads/customer-created.ts +++ b/src/tests/webhook-payloads/customer-created.ts @@ -31,7 +31,7 @@ export default { id: 'pEv_01HD44FHDPMZ3YJB5GEB1EZKQV', webhookMetadata: { webhookTargetId: 'whTarget_01HD4400VTDJQ646V6RY37SR7K', - webhookTargetVersion: '2025-08-06', + webhookTargetVersion: '2026-02-27', webhookDeliveryAttemptId: 'whAttempt_01HD44FJ45FJKVFHM3MDVYPGRS', webhookDeliveryAttemptNumber: 1, webhookDeliveryAttemptTimestamp: '2023-10-19T14:12:25.861Z', diff --git a/src/tests/webhook-payloads/email-received.ts b/src/tests/webhook-payloads/email-received.ts index ebe714f..30fe3aa 100644 --- a/src/tests/webhook-payloads/email-received.ts +++ b/src/tests/webhook-payloads/email-received.ts @@ -106,7 +106,7 @@ export default { id: 'pEv_01HR9W91EMR655WS6VC2867D3C', webhookMetadata: { webhookTargetId: 'whTarget_01HR9VYX2GYKX1XCTFXRG1K3MX', - webhookTargetVersion: '2025-08-06', + webhookTargetVersion: '2026-02-27', webhookDeliveryAttemptId: 'whAttempt_01HR9W92RSJZA4011XDNHJ5VK7', webhookDeliveryAttemptNumber: 1, webhookDeliveryAttemptTimestamp: '2024-03-06T12:37:11.577Z', diff --git a/src/tests/webhook-payloads/invalid.ts b/src/tests/webhook-payloads/invalid.ts index 4ca8c1a..4936b79 100644 --- a/src/tests/webhook-payloads/invalid.ts +++ b/src/tests/webhook-payloads/invalid.ts @@ -48,7 +48,7 @@ export default { id: 'pEv_01HR9W25SFVMS2Y4Q8W75M86G4', webhookMetadata: { webhookTargetId: 'whTarget_01HR9VYX2GYKX1XCTFXRG1K3MX', - webhookTargetVersion: '2025-08-06', + webhookTargetVersion: '2026-02-27', webhookDeliveryAttemptId: 'whAttempt_01HR9W26906XCJ64JQZG8RJCCQ', webhookDeliveryAttemptNumber: 1, webhookDeliveryAttemptTimestamp: '2024-03-06T12:33:25.792Z', diff --git a/src/tests/webhook-payloads/thread-assignment-transitioned.ts b/src/tests/webhook-payloads/thread-assignment-transitioned.ts index c857f17..d6c7b32 100644 --- a/src/tests/webhook-payloads/thread-assignment-transitioned.ts +++ b/src/tests/webhook-payloads/thread-assignment-transitioned.ts @@ -134,7 +134,7 @@ export default { id: 'pEv_01HR9W4K5QEVAFQSYCKN2D8198', webhookMetadata: { webhookTargetId: 'whTarget_01HR9VYX2GYKX1XCTFXRG1K3MX', - webhookTargetVersion: '2025-08-06', + webhookTargetVersion: '2026-02-27', webhookDeliveryAttemptId: 'whAttempt_01HR9W4KV3RHX435FPZJ46P5WY', webhookDeliveryAttemptNumber: 1, webhookDeliveryAttemptTimestamp: '2024-03-06T12:34:45.219Z', diff --git a/src/tests/webhook-payloads/thread-created.ts b/src/tests/webhook-payloads/thread-created.ts index a5c4d75..9e50dd1 100644 --- a/src/tests/webhook-payloads/thread-created.ts +++ b/src/tests/webhook-payloads/thread-created.ts @@ -67,7 +67,7 @@ export default { id: 'pEv_01HD44FHHJ0YABSNGKWMG3CJ5J', webhookMetadata: { webhookTargetId: 'whTarget_01HD4400VTDJQ646V6RY37SR7K', - webhookTargetVersion: '2025-08-06', + webhookTargetVersion: '2026-02-27', webhookDeliveryAttemptId: 'whAttempt_01HD44FJASQM23MNHYDYPAXEG8', webhookDeliveryAttemptNumber: 1, webhookDeliveryAttemptTimestamp: '2023-10-19T14:12:26.073Z', diff --git a/src/tests/webhook-payloads/thread-status-transitioned.ts b/src/tests/webhook-payloads/thread-status-transitioned.ts index 6d58a12..2f1affc 100644 --- a/src/tests/webhook-payloads/thread-status-transitioned.ts +++ b/src/tests/webhook-payloads/thread-status-transitioned.ts @@ -115,7 +115,7 @@ export default { id: 'pEv_01HR9W25SFVMS2Y4Q8W75M86G4', webhookMetadata: { webhookTargetId: 'whTarget_01HR9VYX2GYKX1XCTFXRG1K3MX', - webhookTargetVersion: '2025-08-06', + webhookTargetVersion: '2026-02-27', webhookDeliveryAttemptId: 'whAttempt_01HR9W26906XCJ64JQZG8RJCCQ', webhookDeliveryAttemptNumber: 1, webhookDeliveryAttemptTimestamp: '2024-03-06T12:33:25.792Z', diff --git a/src/webhooks/webhook-schema.json b/src/webhooks/webhook-schema.json index 538ce32..0376a3b 100644 --- a/src/webhooks/webhook-schema.json +++ b/src/webhooks/webhook-schema.json @@ -127,6 +127,7 @@ "thread.thread_field_updated", "thread.thread_field_deleted", "thread.service_level_agreement_status_transitioned", + "thread.thread_tenant_updated", "customer.customer_created", "customer.customer_updated", "customer.customer_deleted", @@ -172,6 +173,42 @@ "type": "string", "format": "date-time" }, + "nullableString": { + "type": [ + "string", + "null" + ] + }, + "nullableDatetime": { + "anyOf": [ + { + "$ref": "#/definitions/datetime" + }, + { + "type": "null" + } + ] + }, + "nullableActor": { + "anyOf": [ + { + "$ref": "#/definitions/actor" + }, + { + "type": "null" + } + ] + }, + "nullableInternalActor": { + "anyOf": [ + { + "$ref": "#/definitions/internalActor" + }, + { + "type": "null" + } + ] + }, "userActor": { "type": "object", "properties": { @@ -306,6 +343,7 @@ "ONLINE", "OFFLINE", "BREAK", + "AWAY", "UNKNOWN_USER_STATUS" ] }, @@ -325,24 +363,10 @@ "$ref": "#/definitions/internalActor" }, "deletedAt": { - "anyOf": [ - { - "$ref": "#/definitions/datetime" - }, - { - "type": "null" - } - ] + "$ref": "#/definitions/nullableDatetime" }, "deletedBy": { - "anyOf": [ - { - "$ref": "#/definitions/internalActor" - }, - { - "type": "null" - } - ] + "$ref": "#/definitions/nullableInternalActor" } }, "required": [ @@ -392,24 +416,10 @@ "$ref": "#/definitions/internalActor" }, "deletedAt": { - "anyOf": [ - { - "$ref": "#/definitions/datetime" - }, - { - "type": "null" - } - ] + "$ref": "#/definitions/nullableDatetime" }, "deletedBy": { - "anyOf": [ - { - "$ref": "#/definitions/internalActor" - }, - { - "type": "null" - } - ] + "$ref": "#/definitions/nullableInternalActor" } }, "required": [ @@ -637,10 +647,7 @@ "minLength": 1 }, "fileExtension": { - "type": [ - "string", - "null" - ] + "$ref": "#/definitions/nullableString" }, "createdAt": { "$ref": "#/definitions/datetime" @@ -959,10 +966,7 @@ "$ref": "#/definitions/attachment/properties/fileMimeType" }, "fileExtension": { - "type": [ - "string", - "null" - ] + "$ref": "#/definitions/nullableString" }, "createdAt": { "$ref": "#/definitions/datetime" @@ -1011,10 +1015,7 @@ "$ref": "#/definitions/attachment/properties/fileMimeType" }, "fileExtension": { - "type": [ - "string", - "null" - ] + "$ref": "#/definitions/nullableString" }, "createdAt": { "$ref": "#/definitions/datetime" @@ -1063,10 +1064,7 @@ "$ref": "#/definitions/attachment/properties/fileMimeType" }, "fileExtension": { - "type": [ - "string", - "null" - ] + "$ref": "#/definitions/nullableString" }, "createdAt": { "$ref": "#/definitions/datetime" @@ -1573,6 +1571,22 @@ } ] }, + "externalId": { + "anyOf": [ + { + "type": "string", + "maxLength": 500 + }, + { + "type": "null" + } + ], + "default": null + }, + "isExcludedFromAi": { + "type": "boolean", + "default": false + }, "createdAt": { "$ref": "#/definitions/datetime" }, @@ -1694,10 +1708,27 @@ "threadPriority": { "type": "number" }, + "threadPriorityFilter": { + "type": "array", + "items": { + "$ref": "#/definitions/threadPriority" + }, + "minItems": 1 + }, "threadAssignee": { "anyOf": [ { - "$ref": "#/definitions/componentRowContent/anyOf/0" + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "UNKNOWN" + } + }, + "required": [ + "type" + ], + "additionalProperties": true }, { "$ref": "#/definitions/user" @@ -1779,7 +1810,17 @@ "threadStatusDetail": { "anyOf": [ { - "$ref": "#/definitions/componentRowContent/anyOf/0" + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "UNKNOWN" + } + }, + "required": [ + "type" + ], + "additionalProperties": true }, { "type": "object", @@ -1962,6 +2003,9 @@ "STRING", "BOOL", "ENUM", + "NUMBER", + "CURRENCY", + "DATE", "UNKNOWN_THREAD_FIELD_SCHEMA_TYPE" ] }, @@ -1977,6 +2021,20 @@ "null" ] }, + "numberValue": { + "type": [ + "number", + "null" + ], + "default": null + }, + "dateValue": { + "type": [ + "string", + "null" + ], + "default": null + }, "createdAt": { "$ref": "#/definitions/datetime" }, @@ -2201,7 +2259,7 @@ "text": { "type": "string", "minLength": 1, - "maxLength": 5000 + "maxLength": 10000 }, "markdown": { "anyOf": [ @@ -2621,7 +2679,17 @@ "serviceLevelAgreement": { "anyOf": [ { - "$ref": "#/definitions/componentRowContent/anyOf/0" + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "UNKNOWN" + } + }, + "required": [ + "type" + ], + "additionalProperties": true }, { "type": "object", @@ -2636,10 +2704,7 @@ "type": "boolean" }, "threadPriorityFilter": { - "type": "array", - "items": { - "$ref": "#/definitions/threadPriority" - } + "$ref": "#/definitions/threadPriorityFilter" }, "createdAt": { "$ref": "#/definitions/datetime" @@ -2688,10 +2753,7 @@ "$ref": "#/definitions/serviceLevelAgreement/anyOf/1/properties/useBusinessHoursOnly" }, "threadPriorityFilter": { - "type": "array", - "items": { - "$ref": "#/definitions/threadPriority" - } + "$ref": "#/definitions/threadPriorityFilter" }, "createdAt": { "$ref": "#/definitions/datetime" @@ -3044,7 +3106,8 @@ "customer", "previousCustomer" ], - "additionalProperties": true + "additionalProperties": true, + "description": "A customer has been created or updated" }, "timelineEntryChangedPayload": { "type": "object", @@ -3061,7 +3124,8 @@ { "type": "null" } - ] + ], + "description": "null if changeType=ADDED" }, "timelineEntry": { "anyOf": [ @@ -3071,7 +3135,8 @@ { "type": "null" } - ] + ], + "description": "null if changeType=REMOVED" }, "changeType": { "type": "string", @@ -3088,7 +3153,8 @@ "timelineEntry", "changeType" ], - "additionalProperties": true + "additionalProperties": true, + "description": "A timeline entry has been added, updated or removed" }, "webhookMetadata": { "type": "object", @@ -3098,7 +3164,7 @@ }, "webhookTargetVersion": { "type": "string", - "const": "2025-08-06" + "const": "2026-02-27" }, "webhookDeliveryAttemptId": { "$ref": "#/definitions/id" @@ -3332,7 +3398,11 @@ "const": "thread.thread_labels_changed" }, "changeType": { - "$ref": "#/definitions/customerGroupMembershipsChangedPayload/properties/changeType" + "type": "string", + "enum": [ + "ADDED", + "REMOVED" + ] }, "thread": { "$ref": "#/definitions/thread" @@ -3501,7 +3571,7 @@ "text": { "type": "string", "minLength": 1, - "maxLength": 5000 + "maxLength": 10000 }, "markdown": { "anyOf": [ @@ -3528,24 +3598,10 @@ "$ref": "#/definitions/internalActor" }, "deletedAt": { - "anyOf": [ - { - "$ref": "#/definitions/datetime" - }, - { - "type": "null" - } - ] + "$ref": "#/definitions/nullableDatetime" }, "deletedBy": { - "anyOf": [ - { - "$ref": "#/definitions/internalActor" - }, - { - "type": "null" - } - ] + "$ref": "#/definitions/nullableInternalActor" } }, "required": [ @@ -3648,6 +3704,15 @@ "type": "string", "const": "thread.slack_message_updated" }, + "changeType": { + "type": "string", + "enum": [ + "MESSAGE_EDITED", + "MESSAGE_DELETED", + "REACTIONS_CHANGED", + "UNKNOWN_SLACK_MESSAGE_CHANGE_TYPE" + ] + }, "thread": { "$ref": "#/definitions/thread" }, @@ -3657,6 +3722,7 @@ }, "required": [ "eventType", + "changeType", "thread", "slackMessage" ], @@ -3782,7 +3848,8 @@ "eventType", "customer" ], - "additionalProperties": true + "additionalProperties": true, + "description": "A customer has been created" }, "customerUpdatedPublicEventPayload": { "type": "object", @@ -3803,7 +3870,8 @@ "customer", "previousCustomer" ], - "additionalProperties": true + "additionalProperties": true, + "description": "A customer has been updated" }, "customerDeletedPublicEventPayload": { "type": "object", @@ -3820,7 +3888,8 @@ "eventType", "previousCustomer" ], - "additionalProperties": true + "additionalProperties": true, + "description": "A customer has been deleted" } }, "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/src/webhooks/webhook-schema.ts b/src/webhooks/webhook-schema.ts index d537780..8ddefdc 100644 --- a/src/webhooks/webhook-schema.ts +++ b/src/webhooks/webhook-schema.ts @@ -25,6 +25,7 @@ export type Actor = | MachineUserActor | SystemActor | CustomerActor; +export type NullableString = string | null; export type EmailActor = | { actorType: "UNKNOWN"; @@ -182,6 +183,8 @@ export type ThreadAssignee = id: string; [k: string]: unknown; }; +export type NullableDatetime = Datetime | null; +export type NullableInternalActor = InternalActor | null; export type ServiceLevelAgreement = | { type: "UNKNOWN"; @@ -191,7 +194,7 @@ export type ServiceLevelAgreement = id: Id; tier: Tier; useBusinessHoursOnly: boolean; - threadPriorityFilter: ThreadPriority[]; + threadPriorityFilter: ThreadPriorityFilter; createdAt: Datetime; createdBy: InternalActor; updatedAt: Datetime; @@ -204,7 +207,7 @@ export type ServiceLevelAgreement = id: Id; tier: Tier; useBusinessHoursOnly: boolean; - threadPriorityFilter: ThreadPriority[]; + threadPriorityFilter: ThreadPriorityFilter; createdAt: Datetime; createdBy: InternalActor; updatedAt: Datetime; @@ -213,6 +216,10 @@ export type ServiceLevelAgreement = nextResponseTimeMinutes: number; [k: string]: unknown; }; +/** + * @minItems 1 + */ +export type ThreadPriorityFilter = [ThreadPriority, ...ThreadPriority[]]; export type ServiceLevelAgreementStatusDetail = | { status: "UNKNOWN"; @@ -310,6 +317,7 @@ export interface WebhooksSchemaDefinition { | "thread.thread_field_updated" | "thread.thread_field_deleted" | "thread.service_level_agreement_status_transitioned" + | "thread.thread_tenant_updated" | "customer.customer_created" | "customer.customer_updated" | "customer.customer_deleted" @@ -320,6 +328,9 @@ export interface WebhooksSchemaDefinition { webhookMetadata: WebhookMetadata; [k: string]: unknown; } +/** + * A customer has been created or updated + */ export interface CustomerChangedPayload { changeType: "ADDED" | "UPDATED"; eventType: "customer.customer_changed"; @@ -397,9 +408,18 @@ export interface CustomerGroupMembershipsChangedPayload { previousCustomer: Customer; [k: string]: unknown; } +/** + * A timeline entry has been added, updated or removed + */ export interface TimelineEntryChangedPayload { eventType: "timeline.timeline_entry_changed"; + /** + * null if changeType=ADDED + */ previousTimelineEntry: TimelineEntry | null; + /** + * null if changeType=REMOVED + */ timelineEntry: TimelineEntry | null; changeType: "ADDED" | "UPDATED" | "REMOVED"; [k: string]: unknown; @@ -441,7 +461,7 @@ export interface ChatEntryAttachment { fileName: string; fileSizeBytes: number; fileMimeType: string; - fileExtension: string | null; + fileExtension: NullableString; createdAt: Datetime; createdBy: Actor; updatedAt: Datetime; @@ -480,7 +500,7 @@ export interface EmailEntryAttachment { fileName: string; fileSizeBytes: number; fileMimeType: string; - fileExtension: string | null; + fileExtension: NullableString; createdAt: Datetime; createdBy: Actor; updatedAt: Datetime; @@ -556,7 +576,7 @@ export interface CustomEntryAttachment { fileName: string; fileSizeBytes: number; fileMimeType: string; - fileExtension: string | null; + fileExtension: NullableString; createdAt: Datetime; createdBy: Actor; updatedAt: Datetime; @@ -600,14 +620,14 @@ export interface User { email: EmailAddress; fullName: string; publicName: string; - status: "ONLINE" | "OFFLINE" | "BREAK" | "UNKNOWN_USER_STATUS"; + status: "ONLINE" | "OFFLINE" | "BREAK" | "AWAY" | "UNKNOWN_USER_STATUS"; statusChangedAt: Datetime; createdAt: Datetime; createdBy: InternalActor; updatedAt: Datetime; updatedBy: InternalActor; - deletedAt: Datetime | null; - deletedBy: InternalActor | null; + deletedAt: NullableDatetime; + deletedBy: NullableInternalActor; [k: string]: unknown; } export interface MachineUser { @@ -619,8 +639,8 @@ export interface MachineUser { createdBy: InternalActor; updatedAt: Datetime; updatedBy: InternalActor; - deletedAt: Datetime | null; - deletedBy: InternalActor | null; + deletedAt: NullableDatetime; + deletedBy: NullableInternalActor; [k: string]: unknown; } export interface Label { @@ -639,6 +659,8 @@ export interface LabelType { isArchived?: boolean; archivedAt: Datetime | null; archivedBy: InternalActor | null; + externalId?: string | null; + isExcludedFromAi?: boolean; createdAt: Datetime; createdBy: InternalActor; updatedAt: Datetime; @@ -698,7 +720,7 @@ export interface Attachment { fileName: string; fileSizeBytes: number; fileMimeType: string; - fileExtension: string | null; + fileExtension: NullableString; createdAt: Datetime; createdBy: Actor; updatedAt: Datetime; @@ -799,9 +821,11 @@ export interface ThreadField { id: Id; threadId: Id; key: string; - type: "STRING" | "BOOL" | "ENUM" | "UNKNOWN_THREAD_FIELD_SCHEMA_TYPE"; + type: "STRING" | "BOOL" | "ENUM" | "NUMBER" | "CURRENCY" | "DATE" | "UNKNOWN_THREAD_FIELD_SCHEMA_TYPE"; stringValue: string | null; booleanValue: boolean | null; + numberValue?: number | null; + dateValue?: string | null; createdAt: Datetime; createdBy: Actor; updatedAt: Datetime; @@ -860,17 +884,26 @@ export interface Tier { updatedBy: InternalActor; [k: string]: unknown; } +/** + * A customer has been created + */ export interface CustomerCreatedPublicEventPayload { eventType: "customer.customer_created"; customer: Customer; [k: string]: unknown; } +/** + * A customer has been updated + */ export interface CustomerUpdatedPublicEventPayload { eventType: "customer.customer_updated"; customer: Customer; previousCustomer: Customer; [k: string]: unknown; } +/** + * A customer has been deleted + */ export interface CustomerDeletedPublicEventPayload { eventType: "customer.customer_deleted"; previousCustomer: Customer; @@ -888,8 +921,8 @@ export interface ThreadNoteCreatedEventPayload { createdBy: InternalActor; updatedAt: Datetime; updatedBy: InternalActor; - deletedAt: Datetime | null; - deletedBy: InternalActor | null; + deletedAt: NullableDatetime; + deletedBy: NullableInternalActor; [k: string]: unknown; }; [k: string]: unknown; @@ -902,6 +935,7 @@ export interface ThreadChatReceivedPublicEventPayload { } export interface ThreadSlackMessageUpdatedEventPayload { eventType: "thread.slack_message_updated"; + changeType: "MESSAGE_EDITED" | "MESSAGE_DELETED" | "REACTIONS_CHANGED" | "UNKNOWN_SLACK_MESSAGE_CHANGE_TYPE"; thread: Thread; slackMessage: SlackMessage; [k: string]: unknown; @@ -944,7 +978,7 @@ export interface ThreadDiscordMessageUpdatedEventPayload { } export interface WebhookMetadata { webhookTargetId: Id; - webhookTargetVersion: "2025-08-06"; + webhookTargetVersion: "2026-02-27"; webhookDeliveryAttemptId: Id; webhookDeliveryAttemptNumber: number; webhookDeliveryAttemptTimestamp: Datetime;