Webhook events and payloads
Understand Sydnee webhook payloads through TypeScript examples, verify their signatures, and handle every available event.
Sydnee sends the workspace events you choose to your HTTPS endpoint as JSON. The body includes its version. You can use any language that accepts HTTPS requests and reads JSON. This page shows the event body and types with TypeScript examples. Use GET /v1/webhook-event-types to see the event types your workspace can use.
Understand the webhook body
Every live event uses the JSON payload shape shown in the TypeScript example below. Every key is present. Some values can be null or an empty array.
export type SydneeWebhookJsonValue =
| string
| number
| boolean
| null
| SydneeWebhookJsonValue[]
| { [key: string]: SydneeWebhookJsonValue };
export type SydneeWebhookEventCore = {
id: string;
event_type: SydneeWebhookEventType;
event_version: 1;
occurred_at: string;
operation_id: string | null;
account_id: string | null;
actor_type: "portalUser" | "teamUser" | "system";
actor_id: number | null;
object_type: SydneeWebhookObjectType;
object_id: string | number;
task_board_id: string | null;
parent_object_type: "task" | "file" | "taskBoardStatusUpdate" | null;
parent_object_id: string | number | null;
resource_url: string | null;
app_url: string | null;
};Event-specific fields use the same keys for every delivery:
export type SydneeWebhookEventDetails = {
request_field_id: string | null;
request_field_type: string | null;
request_field_label: string | null;
request_field_value: SydneeWebhookJsonValue;
status: string | null;
title: string | null;
email: string | null;
changes: string[];
conversation_id: string | null;
conversation_type: "account" | "channel" | "dm" | null;
message_origin: "web" | "rest" | "email" | "import" | null;
message_text: string | null;
message_has_attachment: boolean | null;
message_has_location: boolean | null;
comment_text: string | null;
status_update_text: string | null;
status_update_coming_next_text: string | null;
file_version_number: number | null;
file_change_note: string | null;
reaction_emoji_key: string | null;
reaction_emoji: string | null;
};
export type SydneeWebhookEvent = SydneeWebhookEventCore &
SydneeWebhookEventDetails;The current object types are:
export type KnownSydneeWebhookObjectType =
| "account"
| "portalUser"
| "request"
| "task"
| "taskAttachment"
| "comment"
| "file"
| "deliverable"
| "taskBoardStatusUpdate"
| "liveChatMessage";
export type SydneeWebhookObjectType =
| KnownSydneeWebhookObjectType
| (string & {});Use these event-type groups to build the SydneeWebhookEventType union:
type AccountEventType =
| "account.created"
| "account.archived"
| "account.restored"
| "account.deleted";
type PortalUserEventType =
| "portalUser.added"
| "portalUser.removed"
| "portalUser.accessed";
type RequestEventType =
| "request.published"
| "request.unpublished"
| "request.completed"
| "request.field.completed"
| "request.reopened"
| "request.archived"
| "request.unarchived"
| "request.deleted";type TaskEventType =
| "task.created"
| "task.updated"
| "task.completed"
| "task.reopened"
| "task.archived"
| "task.unarchived"
| "task.deleted"
| "task.attachment.added";
type CommentEventType =
| "task.comment.created"
| "file.comment.created"
| "taskBoard.statusUpdate.comment.created";
type ReactionEventType =
| "file.comment.reaction.added"
| "task.comment.reaction.added"
| "taskBoard.statusUpdate.reaction.added"
| "taskBoard.statusUpdate.comment.reaction.added";
type FileEventType =
| "file.uploaded"
| "file.version.uploaded"
| "file.renamed"
| "file.moved"
| "file.deleted";type DeliverableEventType =
| "deliverable.published"
| "deliverable.viewed"
| "deliverable.removed";
type OtherEventType =
| "service.requested"
| "service.ticket.created"
| "taskBoard.statusUpdate.created";
type LiveChatEventType = "liveChat.message.sent";
export type KnownSydneeWebhookEventType =
| AccountEventType
| PortalUserEventType
| RequestEventType
| TaskEventType
| CommentEventType
| ReactionEventType
| FileEventType
| DeliverableEventType
| OtherEventType
| LiveChatEventType;
export type SydneeWebhookEventType =
| KnownSydneeWebhookEventType
| (string & {});Sydnee may add event types without changing version 1, but a new type is sent only after you subscribe to it. In TypeScript, the string & {} branch keeps the type open while preserving editor hints for known events. Use GET /v1/webhook-event-types to load the current list.
Read each field
Use the event ID and version to control delivery. Store the ID before you start slower work. Use it to skip repeats. Read the event type and object ID together. The Type column uses TypeScript notation. Use the matching types in your language.
| Field | Type | Meaning |
|---|---|---|
id | string | Stable event ID. It matches X-Sydnee-Event-Id. Use it to detect duplicate deliveries. |
event_type | SydneeWebhookEventType | The change that occurred. |
event_version | 1 | Schema version for the JSON envelope. |
occurred_at | string | ISO 8601 time when the change occurred. |
operation_id | string | null | Opaque correlation ID for diagnostics. Separate events from one action may have different values. |
account_id | string | null | Public account ID returned by the Accounts API when the event belongs to an account. |
actor_type | "portalUser" | "teamUser" | "system" | Who caused the event. |
actor_id | number | null | The account-scoped user ID returned by the account users API. Use actor_type to determine whether it identifies a portal user or team user. |
object_type | SydneeWebhookObjectType | Resource identified by object_id. |
object_id | string | number | Stable public ID for the resource named by object_type. Its JSON type matches the public API. |
task_board_id | string | null | Public Task Board ID for Tasks, Task comments, attachments, status updates, and status-update comments, including their reactions. Other events use null. |
parent_object_type | "task" | "file" | "taskBoardStatusUpdate" | null | Parent resource type for a comment, Task attachment, or subtask. Other events use null. |
parent_object_id | string | number | null | Public Task ID, numeric file ID, or status-update ID for the parent. Other events use null. |
resource_url | string | null | Public API URL for an exact object GET. It is null when no exact read endpoint is currently available. |
app_url | string | null | Authenticated Sydnee app link to the affected resource when it still has a useful destination. |
request_field_id | string | null | Completed field ID for request.field.completed. Other events use null. |
request_field_type | string | null | Request field type for request.field.completed. Other events use null. |
request_field_label | string | null | Request field label. It is set only when Include authored text and resource titles is on. |
request_field_value | SydneeWebhookJsonValue | Submitted non-sensitive Request answer. It is set only when Include authored text and resource titles is on. |
status | string | null | Resource state after the event. It is null when the resource has no status field. |
title | string | null | Resource name or title. It is set only when Include authored text and resource titles is on. |
email | string | null | Portal user email. It is set only when Include portal user email addresses is on. |
changes | string[] | Public API property names changed by the event. Other event families use an empty array. |
conversation_id | string | null | TalkJS conversation ID for liveChat.message.sent. Other events use null. |
conversation_type | "account" | "channel" | "dm" | null | Conversation type for liveChat.message.sent. Other events use null. |
message_origin | "web" | "rest" | "email" | "import" | null | How the portal user sent the Live Chat message. Other events use null. |
message_text | string | null | Up to 280 characters of portal-authored Live Chat text. It is set only when authored text is on. |
message_has_attachment | boolean | null | Whether the live chat message has an attachment. The attachment and its URL are not included. |
message_has_location | boolean | null | Whether the live chat message has a location. The coordinates are not included. |
comment_text | string | null | Plain-text Task, file, or status-update comment preview, up to 280 characters, when authored text is on. |
status_update_text | string | null | Plain-text Task Board update preview, up to 280 characters, when authored text is on. |
status_update_coming_next_text | string | null | Plain-text “coming next” preview, up to 280 characters, when authored text is on. |
file_version_number | number | null | File version number for file.version.uploaded. This is available whether authored text is on or off. |
file_change_note | string | null | Plain-text file-version change-note preview, up to 280 characters, when authored text is on. |
reaction_emoji_key | string | null | Canonical emoji key on a reaction-added event. Matches API reaction emojiKey. Other events use null. |
reaction_emoji | string | null | Display emoji on a reaction-added event. Matches API reaction emoji. Other events use null. |
Use actor_type to choose the teamUsers or portalUsers list. Then match actor_id directly to an id from GET /v1/accounts/{accountId}/users. The ID belongs to the client account in account_id. It is never a global team-user or portal-member ID.
For actions run by software, actor_type is system and actor_id is null. This includes Public API calls and Zapier actions. The API-key owner grants access for the call. They are not returned as its actor. Sometimes Sydnee knows the actor type but cannot safely find their account user ID. In that case, actor_id is null. The actor_type stays portalUser or teamUser.
Look up the affected object
Use object_type and object_id together. Its JSON type matches the public API. Numeric resources use a number. String IDs and provider IDs use a string.
| Event family | Object identified by object_id | Follow-up lookup |
|---|---|---|
| Account | Public Account ID | Use resource_url while the account is readable. |
| Portal user | Numeric account-scoped user ID from the account users API | No exact-object GET currently exists, so resource_url is null. |
| Request | Numeric Request ID | Use resource_url for non-deleted Request events. Historical deliveries may still contain null. |
| Task | Public Task ID | Use resource_url. task_board_id contains the public Task Board ID. |
| Task attachment | Numeric Task attachment ID | Use resource_url to request a fresh signed download URL. The parent fields identify the Task. |
| Task or file comment | Numeric comment ID | No exact-comment GET currently exists. Use the parent fields for context. |
| Status-update comment | Public string comment ID | Use resource_url when present. The parent identifies the status update. |
| File | Numeric public file ID | Use resource_url while the file is readable. For file.version.uploaded, this is the file ID, not a version ID. |
| Deliverable | Public Deliverable ID | No public read route currently exists, so resource_url is null. |
| Service request | Public ID of the resulting Task | Use resource_url. task_board_id contains the public Task Board ID. |
| Task Board status update | Public status-update ID | Use resource_url when present. Earlier events can keep null. |
| Live Chat message | TalkJS message ID | No public read route currently exists, so resource_url is null. |
A non-null resource_url is a machine-oriented URL on https://public-api.sydnee.app. Send the same Bearer API key you use for other public API requests. resource_url is null when no exact read endpoint is currently available. Historical Request deliveries may also contain null because the field remains nullable in event version 1.
Deleted and removed objects keep resource_url: null because the affected object no longer exists. Do not build a public API URL when this field is null. Use the context fields or a documented collection lookup instead.
app_url is the human-oriented link to the affected place in Sydnee. It always requires normal Sydnee authentication and authorization. It is not a capability link, and it is not controlled by the authored-text setting.
| Event family | app_url destination |
|---|---|
| Account created or restored; portal-user events | Account overview |
| Request events except deletion | Request view |
| Task lifecycle/update events except deletion; Task comments and attachments; service events | Exact Task on its Task Board |
| File uploads, versions, renames, moves, and comments | File viewer with the relevant panel open; file comments also select the exact comment |
| Deliverable published or viewed | Exact Deliverable |
| Task Board status update or reaction | Exact Board overview and update |
| Status-update comment or comment reaction | Exact status update with the comment selected |
| Live Chat message | Exact conversation, including accountless conversations |
| Deleted or removed Account, Request, Task, file, or Deliverable | null |
For a comment, object_type is comment. Task and file comment IDs are numeric.
Their parent type is task or file, with a string Task ID or numeric file ID.
Status-update comment IDs are strings. Their parent type is
taskBoardStatusUpdate, and their parent ID is the status-update ID. Task and
status-update comments also include task_board_id.
For a Task attachment, object_type is taskAttachment. Its object_id is the numeric attachment ID. The parent fields identify the Task. For a subtask, the parent fields identify the parent Task. Other events use null parent fields.
event_type describes the transition. status is the current persisted resource state after that transition.
| Resource | status values |
|---|---|
| Task lifecycle and update events | open or complete |
service.requested and service.ticket.created | open |
| Request | draft, scheduled, published, or completed |
| Task Board status-update-created event | ON_TRACK, AT_RISK, OFF_TRACK, ON_HOLD, COMPLETE, or DROPPED |
| Reaction-added event | null |
| Account, portal user, comment, Task attachment, file, Deliverable, and Live Chat message | null |
status never uses transition words such as created or deleted.
Choose what the endpoint receives
You can manage endpoints in Company Settings → Webhooks or through the /v1/webhooks API. The generated API reference lists each management route and request shape.
Each endpoint has four controls:
- Select one or more event types.
- Send events for every client account or only selected accounts.
- Send activity from all people, team members only, or portal users only.
- Choose whether to include authored text and portal user email addresses.
All people includes team members, portal users, and automated system activity. Team members only matches actor_type: "teamUser". Portal users only matches actor_type: "portalUser". Public API changes and Zapier actions use actor_type: "system". They are sent only when All people is selected.
An endpoint limited to selected client accounts receives only events with a matching account_id. The management API accepts those public IDs in accountIds. Accountless Live Chat channels and direct messages can go only to endpoints set to every account.
Handle event-specific fields
Most events need only the shared fields. These cases add more context:
request.field.completedsets the field ID and type. It also setschangesto["requestFieldValue"]. This matches the public API property name. When Include authored text and resource titles is enabled,request_field_labelandrequest_field_valuecontain the non-sensitive answer context. Theobject_idremains the Request ID.task.updatedadds one or more public Task property names tochanges. If you use TypeScript, keep this field typed asstring[]so new public properties remain forward compatible.- Task objects include
task_board_idand an exactresource_url. Subtasks identify their parent Task when the creating workflow supplies parent context. Comment and Task attachment events identify the child object and use the parent fields for context. portalUser.added,portalUser.removed, andportalUser.accessedcan includeemailwhen Include portal user email addresses is enabled for the endpoint.service.requestedidentifies the Task created when a portal user requests a service that is not yet active.service.ticket.createdidentifies the Task created when a portal user submits a ticket for an active service.- Both service events use
object_type: "task", the public Task ID inobject_id, and the public Task Board ID intask_board_id. liveChat.message.sentidentifies the TalkJS message inobject_idand adds conversation and message metadata. It is emitted only for portal-user messages.
The authored-text setting can add resource titles and non-sensitive Request answers. It can also add previews of Task, file, and status-update comments; Task Board update and coming-next text; file-version change notes; and Live Chat text sent by portal users. Previews use plain text with extra spaces removed. Each is capped at 280 characters. Both content settings are off by default. A field returns null when its setting is off or does not apply. The key stays in the body. app_url, file_version_number, and reaction emoji fields do not need authored text to be on. Reaction-added events keep text previews and status null.
Sensitive Request answers and signature images are never included. The same rule applies to file-upload internals, file contents, direct storage URLs, location coordinates, and raw provider metadata. File uploads still produce the field-completed event. For those events, request_field_value remains null.
See an example payload
This example shows request.field.completed with authored text enabled and portal user email addresses turned off.
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"event_type": "request.field.completed",
"event_version": 1,
"occurred_at": "2026-09-15T18:42:11.000Z",
"operation_id": "event-1234567890123",
"account_id": "acct_123",
"actor_type": "portalUser",
"actor_id": 2903,
"object_type": "request",
"object_id": 1234567890123,
"task_board_id": null,
"parent_object_type": null,
"parent_object_id": null,
"resource_url": "https://public-api.sydnee.app/v1/accounts/acct_123/requests/1234567890123",
"app_url": "https://my.sydnee.app/requests/1234567890123/view",
"request_field_id": "1234567890124",
"request_field_type": "email",
"request_field_label": "Personal email address",
"request_field_value": "client@example.com",
"status": "published",
"title": null,
"email": null,
"changes": ["requestFieldValue"],
"conversation_id": null,
"conversation_type": null,
"message_origin": null,
"message_text": null,
"message_has_attachment": null,
"message_has_location": null,
"comment_text": null,
"status_update_text": null,
"status_update_coming_next_text": null,
"file_version_number": null,
"file_change_note": null,
"reaction_emoji_key": null,
"reaction_emoji": null
}Use the current event catalog
Choose events in Company Settings → Webhooks, or get the live list from GET /v1/webhook-event-types.
Account events
Account events use object_type: "account". object_id is the public Account ID. A readable account uses https://public-api.sydnee.app/v1/accounts/{accountId} as resource_url. Archived accounts currently use null, but may receive an exact lookup URL if the API adds one. Deleted accounts always use null.
| Event type | Sent when |
|---|---|
account.created | A client account is created. |
account.archived | A client account is moved to the archive. |
account.restored | An archived client account is restored. |
account.deleted | A client account is deleted. |
Portal-user events
Portal user events use object_type: "portalUser". object_id is the affected user's numeric account-scoped membership ID. It matches an id returned by GET /v1/accounts/{accountId}/users. That API currently returns a list rather than one exact user, so resource_url is null. portalUser.removed always keeps it null.
For portalUser.removed, object_id is the former membership ID. The membership no longer exists after removal, so it cannot be retrieved from the account-users collection.
| Event type | Sent when |
|---|---|
portalUser.added | A portal user gains access to a client account. |
portalUser.removed | A portal user’s access to a client account is removed. |
portalUser.accessed | A portal user starts a new access session for a client account. |
Request events
Request events use object_type: "request". object_id is the numeric Request ID. Sydnee sets this authenticated lookup URL on new non-deleted Request events:
https://public-api.sydnee.app/v1/accounts/{account_id}/requests/{object_id}The endpoint returns the Request’s status, active fields, and latest saved answers. It is a snapshot, not answer history. It doesn't return answer updatedAt. An answer’s createdAt is when Sydnee saved that answer. Sensitive answers, signatures, and encrypted values remain hidden.
The snapshot returns all active fields without running conditional logic. A hidden field can retain and return its latest saved answer.
File answers contain safe metadata and authenticated file-detail and download paths. They never contain storage keys or signed URLs. Historical deliveries may still have resource_url: null. The request.deleted event always keeps it null.
The Request resource and webhook use the same IDs and property names. A later snapshot can contain a newer answer than the event:
| Request resource | Webhook payload |
|---|---|
request.id | object_id |
request.accountId | account_id |
field.id | request_field_id |
field.type | request_field_type |
field.label | request_field_label |
currentAnswer.requestFieldValue | request_field_value |
currentAnswer.actor.id | actor_id |
currentAnswer.actor.kind | actor_type |
For request.field.completed, request_field_value contains the portal user’s answer when Include authored text and resource titles is on. Sensitive fields, signatures, and file uploads return null. This portal-only event represents the field’s first completion. Later edits do not emit it again.
Fetch resource_url after request.completed for the final current snapshot. Poll the Request resource if you also need later edits.
| Event type | Sent when |
|---|---|
request.published | A Request is published for portal users. |
request.unpublished | A published Request returns to an unpublished state. |
request.completed | A portal user submits a Request, or a team member marks it complete. |
request.field.completed | A portal user saves a response that completes a field. Sydnee sends this once. Later edits do not repeat it. |
request.reopened | A completed Request is reopened. |
request.archived | A Request is moved to the archive. |
request.unarchived | An archived Request is restored. |
request.deleted | A Request is deleted. |
Task events
Task lifecycle and update events use object_type: "task". object_id is the public Task ID, and task_board_id is the public Task Board ID. When the Task remains readable, resource_url has this form:
https://public-api.sydnee.app/v1/accounts/{account_id}/task-boards/{task_board_id}/tasks/{object_id}task.deleted uses resource_url: null. Other Task events include the URL, including events for an archived Task.
| Event type | Sent when |
|---|---|
task.created | A Task is created. |
task.updated | A supported Task detail or link changes. Sydnee queues the event when it saves the change. It does not add a wait. |
task.completed | A Task is completed. Sydnee schedules the event 45 seconds after the action. The visible Undo window lasts eight seconds. |
task.reopened | A completed Task is reopened. Sydnee schedules the event 45 seconds after the action. The visible Undo window lasts eight seconds. |
task.archived | A Task is moved to the archive. |
task.unarchived | An archived Task is restored. |
task.deleted | A Task is deleted. |
task.attachment.added | An attachment is added to a Task. |
Subtask events set parent_object_type: "task" and put the parent Task ID in parent_object_id whenever the Task has a parent.
task.attachment.added is a separate Task event. It uses object_type: "taskAttachment". The numeric attachment ID is in object_id, and the Task ID is in parent_object_id. Its resource_url calls the public API route that returns a fresh signed download URL:
https://public-api.sydnee.app/v1/accounts/{account_id}/task-boards/{task_board_id}/tasks/{parent_object_id}/attachments/{object_id}/downloadtask.updated covers:
- Title, details, due date, or priority
- Section or Task type
- Assigned people
- Tags or links between Tasks
- Portal user view or edit access
Each item in changes names what changed. These are the current values:
type TaskUpdatedChange =
| "assignees"
| "collaborators"
| "description"
| "due"
| "title"
| "sectionId"
| "clientAccess"
| "priority"
| "kind"
| "isBlocked"
| "tags"
| (string & {});Completion and reopening use their own events, not task.updated. Undo remains visible for eight seconds. Sydnee holds outbound delivery for 45 seconds as an additional safety buffer and keeps occurred_at set to the original action time.
Comment events
Comment events use object_type: "comment" and put the comment ID in object_id. They identify the parent separately. When authored text is on, comment_text contains a plain-text preview of up to 280 characters. Task/file comments have no exact-comment GET, so their resource_url is null. Status comments can return their exact public API URL.
| Event type | Parent type | Parent ID | Sent when |
|---|---|---|---|
task.comment.created | task | Public Task ID; also sets task_board_id | A comment is added to a Task. |
file.comment.created | file | Numeric public file ID | A top-level comment or reply is added to a file. |
taskBoard.statusUpdate.comment.created | taskBoardStatusUpdate | Public string status-update ID; also sets task_board_id | A comment is added to a status update. |
Use the parent fields and the matching collection route when you need to locate a comment:
GET /v1/accounts/{accountId}/task-boards/{boardId}/tasks/{taskId}/comments
GET /v1/accounts/{accountId}/files/{fileId}/comments
GET /v1/accounts/{accountId}/task-boards/{boardId}/status-updates/{statusUpdateId}/commentsStatus-update comments have string IDs. Their app_url opens the discussion
with that comment selected. A non-null resource_url reads that exact comment:
GET /v1/accounts/{accountId}/task-boards/{boardId}/status-updates/{statusUpdateId}/comments/{commentId}Earlier events and events sent before public lookups are ready can keep null.
Reaction events
Reaction events identify the comment or status update that received the emoji.
actor_type and actor_id identify the person who added it. The ID is the same
account user ID used in API reaction reads.
Reaction events require a teamUser or portalUser actor with a non-null
account user ID. System authors can receive reactions, but System is not the
reactor on these events.
| Event type | Object type | Object ID | Parent |
|---|---|---|---|
file.comment.reaction.added | comment | Numeric file-comment ID | file and numeric file ID |
task.comment.reaction.added | comment | Numeric Task-comment ID | task and public Task ID |
taskBoard.statusUpdate.reaction.added | taskBoardStatusUpdate | Public status-update ID | Both fields null |
taskBoard.statusUpdate.comment.reaction.added | comment | Public string status-comment ID | taskBoardStatusUpdate and public status-update ID |
reaction_emoji_key is the canonical key. reaction_emoji is the display emoji.
They match emojiKey and emoji in API reaction reads. These fields are null on
other events. Older stored events may lack them. Treat a missing value as null.
An add sends one event when the emoji was absent. Adding it again while it is present sends no new event. Removal sends no event. Removing then adding it again sends a new event. This also applies to self-reactions and reactions that send no notification. Delivery retries keep the same event ID.
Reaction events have status: null and no authored-text previews.
Task/file comment reaction resource_url values stay null. Status-update and
status-comment reactions can return their exact public API URL when that lookup
is available. Historical null values remain valid. app_url opens the affected
place in Sydnee. Each reaction type needs its own event selection;
existing comment-created selections stay unchanged.
File events
File events use object_type: "file". object_id is the numeric public file ID. For file.version.uploaded, it remains the file ID; file_version_number identifies the uploaded version. While the file remains readable, resource_url points to its exact public API route. file.deleted uses resource_url: null.
| Event type | Sent when |
|---|---|
file.uploaded | A new file is uploaded. |
file.version.uploaded | A new version is uploaded for an existing file. |
file.renamed | A file’s name changes. |
file.moved | A file moves to another folder. |
file.deleted | A file is deleted. |
Deliverable events
Deliverable events use object_type: "deliverable". object_id is the public Deliverable ID. resource_url is currently null because Deliverables do not have a public read route. deliverable.removed always keeps it null.
| Event type | Sent when |
|---|---|
deliverable.published | A folder is published as a Deliverable for portal users. |
deliverable.viewed | A portal user opens a published Deliverable for the first time. |
deliverable.removed | A Deliverable is removed. |
A view records that the Deliverable page opened. It does not record approval.
Service and status-update events
These events cover service requests from portal users and Task Board updates.
| Event type | Object type | Sent when |
|---|---|---|
service.requested | task | A portal user requests a service that is not yet active. |
service.ticket.created | task | A portal user creates a ticket for an active service. |
taskBoard.statusUpdate.created | taskBoardStatusUpdate | A status update is posted to a Task Board. |
For service.requested and service.ticket.created, object_id, task_board_id, and resource_url identify the resulting Task and its board. For taskBoard.statusUpdate.created, object_id is the public status-update ID. The task_board_id is the public Task Board ID. A non-null resource_url reads the exact update; older events can keep null. When authored text is on, status_update_text and status_update_coming_next_text contain plain-text previews.
GET /v1/accounts/{accountId}/task-boards/{boardId}/status-updates/{statusUpdateId}The service event represents the complete Task creation. Follow resource_url to read its initial assignees and collaborators. Sydnee does not send separate task.updated events for those relationships during creation. Changes made afterward continue to send task.updated normally.
Live Chat events
liveChat.message.sent uses object_type: "liveChatMessage". object_id is the TalkJS message ID. resource_url is currently null because Live Chat messages do not have a public API read route.
| Event type | Sent when |
|---|---|
liveChat.message.sent | A portal user sends a Live Chat message. The message can contain text, an attachment, or a location. |
The event works with account chats. It also works with channels and direct messages. It skips team and system messages. It skips read receipts and typing state. It also skips notices and repaired past events.
For a channel or direct message without a client-account link, account_id is null. An endpoint may be limited to selected client accounts. It gets the event only when the chat belongs to one of those accounts. An endpoint set to every account can also get accountless chats.
message_has_attachment and message_has_location show the form of the message. They do not expose an attachment URL or location. message_text is null unless Include authored text and resource titles is on. When it is on, the field has a plain-text preview of up to 280 characters. app_url opens the exact conversation even when account_id is null.
Verify the signature
Sydnee signs timestamp + "." + exact_raw_body with a hash-based message authentication code using SHA-256 (HMAC-SHA256). Check the signature before you parse or process the body.
Live events, tests, and checks include the headers below. The previous-signature header appears only during secret rotation.
| Header | Value |
|---|---|
X-Sydnee-Webhook-Timestamp | Unix time in seconds used for the signature. |
X-Sydnee-Signature | Current signature in v1=hex_digest form. |
X-Sydnee-Signature-Previous | Optional previous-secret signature during the 24-hour rotation overlap. |
X-Sydnee-Event-Id | Stable event ID. |
X-Sydnee-Delivery-Id | Delivery ID for this endpoint. |
X-Sydnee-Attempt-Id | Unique ID for this delivery attempt. |
This TypeScript example for Node.js checks the exact body bytes. It does not check timestamp age. Reject stale timestamps based on the clock skew your endpoint allows.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifySydneeWebhook(
rawBody: Buffer,
timestamp: string,
signature: string,
secret: string,
) {
if (!/^v1=[0-9a-f]{64}$/i.test(signature)) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.`)
.update(rawBody)
.digest();
const received = Buffer.from(signature.slice(3), "hex");
return (
received.length === expected.length && timingSafeEqual(received, expected)
);
}Keep the signing secret in a server-side secret store. Sydnee reveals a new secret once when you create an endpoint or rotate its secret.
Handle retries and duplicates
Webhook delivery is at least once. Your server may finish a request before a timeout is known. A later try can then send the same event.
Events can arrive in a different order. occurred_at records when the source action happened, not when your endpoint got it. Use that time when order matters. Your code also needs to handle events that happen at the same time or arrive out of order.
If current resource state matters, follow resource_url after storing the event. Do not apply deliveries as an ordered sequence of changes.
- Verify the signature against the raw body.
- Store
idwith a unique constraint. - Return a
2xxresponse after the event is stored. - Process slower work from your own queue.
- Ignore an event whose
idwas already stored.
Sydnee retries network failures, timeouts, 408, 425, 429, and 5xx responses. After the first failed attempt, it can retry up to six times. The first waits are 1 minute, 5 minutes, and 30 minutes. Later waits are 2 hours, 8 hours, and 24 hours. A valid Retry-After header can delay the next retry. It cannot shorten the default wait. The delay is capped at 24 hours. Other 4xx responses end that delivery. Sydnee does not follow redirects.
Distinguish test and verification deliveries
Destination checks and Send test posts are callbacks, not SydneeWebhookEvent objects. Their body has zero bytes. They still include the signing and delivery headers. The signature covers that exact empty body. Verify the signature, but do not pass these callbacks to your JSON parser.
- A verification delivery uses
webhook.verificationin delivery history. ItsX-Sydnee-Event-Idstarts withverification_. - A test delivery uses
webhook.testin delivery history. ItsX-Sydnee-Event-Idstarts withtest_. - Return
2xxfor the empty request after its signature passes.
For an empty-body post, use X-Sydnee-Delivery-Id to detect a retry. A delivery ID stays the same across retries. X-Sydnee-Attempt-Id changes for each try.
Only live event posts contain a SydneeWebhookEvent JSON body.
Manage endpoint delivery
A new endpoint starts in verification. Sydnee sends an empty-body check to the destination. A 2xx response makes the endpoint active. A failed check appears in Event deliveries. Fix the destination, then retry the check there.
Custom destinations must use public HTTPS on port 443. Do not include credentials or URL fragments. The host cannot be local, private, or reserved. Sydnee checks the public address before delivery and does not follow redirects. The connection timeout is 3 seconds. The total request timeout is 10 seconds. Requests and responses have a 64 KB size limit.
The endpoint detail page has Overview and Event deliveries tabs. Delivery history keeps 30 days of data. The newest 50 deliveries load first. Scroll to load 50 more. While a loaded delivery is active, the table refreshes every 3 seconds until it finishes. A canceled row explains its reason when you hover over it or focus it with a keyboard.
Disabling an endpoint stops new deliveries and cancels queued work. Enabling it later does not replay missed events. Deleting an endpoint is permanent and also cancels queued work.
Rotating the signing secret shows the new secret once. The old secret remains valid for 24 hours, and Sydnee sends both signatures during that overlap.
Keep version 1 consumers compatible
Version 1 keeps each field's type and meaning stable. A field that allows null keeps that option. It may start returning a value once that value is documented and ready. For example, a resource that still exists may gain an exact resource_url. The field's type stays string | null.
Sydnee may add keys that allow null. Ignore keys your code does not know. New event types are sent only after you choose them. changes stays an open string[], not a closed enum. The known values are listed above. Your code must also ignore unknown values. Sydnee adds a value only after the matching Public API field is documented.
These changes require a new event_version:
- Removing or renaming a field
- Adding a required field
- Changing a field's type or nullability
- Changing a field's meaning
Additive nullable fields and newly subscribed event types remain compatible with version 1.
Who can use webhooks?
Workspace owners on a paid plan can manage webhook endpoints. Core supports one custom endpoint, Growth supports 10, and Power Team supports 25. The initial free trial without a payment method does not include webhook access.
Sydnee does not replay events that occurred before an endpoint was created or while delivery was disabled.
Report API errors from an AI agent
If an AI agent gets an error from the Sydnee Public API, encourage it to submit a report to POST https://public-api.sydnee.app/v1/agent-report. Include the endpoint and HTTP status. Also include the error response and the task the agent was trying to complete. This helps Sydnee find repeated failures and investigate them sooner.
The report endpoint does not require an API key. Send JSON with the required fields below. requestBody is optional.
{
"endpoint": "PATCH /v1/accounts/{accountId}",
"errorCode": "409",
"errorMessage": "Describe the API error response",
"context": "Describe what the agent was trying to complete"
}A valid report returns 200 with { "received": true }. Sydnee stores the complete JSON report in Sentry so the team can investigate the failure. This includes endpoint IDs and query strings, errorCode, errorMessage, context, all requestBody values, and any extra JSON fields. HTTP headers and cookies are not stored. Never include API keys, signing secrets, or client data because every JSON field you submit is retained.