Skip to content
Skip to content

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.

FieldTypeMeaning
idstringStable event ID. It matches X-Sydnee-Event-Id. Use it to detect duplicate deliveries.
event_typeSydneeWebhookEventTypeThe change that occurred.
event_version1Schema version for the JSON envelope.
occurred_atstringISO 8601 time when the change occurred.
operation_idstring | nullOpaque correlation ID for diagnostics. Separate events from one action may have different values.
account_idstring | nullPublic account ID returned by the Accounts API when the event belongs to an account.
actor_type"portalUser" | "teamUser" | "system"Who caused the event.
actor_idnumber | nullThe 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_typeSydneeWebhookObjectTypeResource identified by object_id.
object_idstring | numberStable public ID for the resource named by object_type. Its JSON type matches the public API.
task_board_idstring | nullPublic 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" | nullParent resource type for a comment, Task attachment, or subtask. Other events use null.
parent_object_idstring | number | nullPublic Task ID, numeric file ID, or status-update ID for the parent. Other events use null.
resource_urlstring | nullPublic API URL for an exact object GET. It is null when no exact read endpoint is currently available.
app_urlstring | nullAuthenticated Sydnee app link to the affected resource when it still has a useful destination.
request_field_idstring | nullCompleted field ID for request.field.completed. Other events use null.
request_field_typestring | nullRequest field type for request.field.completed. Other events use null.
request_field_labelstring | nullRequest field label. It is set only when Include authored text and resource titles is on.
request_field_valueSydneeWebhookJsonValueSubmitted non-sensitive Request answer. It is set only when Include authored text and resource titles is on.
statusstring | nullResource state after the event. It is null when the resource has no status field.
titlestring | nullResource name or title. It is set only when Include authored text and resource titles is on.
emailstring | nullPortal user email. It is set only when Include portal user email addresses is on.
changesstring[]Public API property names changed by the event. Other event families use an empty array.
conversation_idstring | nullTalkJS conversation ID for liveChat.message.sent. Other events use null.
conversation_type"account" | "channel" | "dm" | nullConversation type for liveChat.message.sent. Other events use null.
message_origin"web" | "rest" | "email" | "import" | nullHow the portal user sent the Live Chat message. Other events use null.
message_textstring | nullUp to 280 characters of portal-authored Live Chat text. It is set only when authored text is on.
message_has_attachmentboolean | nullWhether the live chat message has an attachment. The attachment and its URL are not included.
message_has_locationboolean | nullWhether the live chat message has a location. The coordinates are not included.
comment_textstring | nullPlain-text Task, file, or status-update comment preview, up to 280 characters, when authored text is on.
status_update_textstring | nullPlain-text Task Board update preview, up to 280 characters, when authored text is on.
status_update_coming_next_textstring | nullPlain-text “coming next” preview, up to 280 characters, when authored text is on.
file_version_numbernumber | nullFile version number for file.version.uploaded. This is available whether authored text is on or off.
file_change_notestring | nullPlain-text file-version change-note preview, up to 280 characters, when authored text is on.
reaction_emoji_keystring | nullCanonical emoji key on a reaction-added event. Matches API reaction emojiKey. Other events use null.
reaction_emojistring | nullDisplay 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 familyObject identified by object_idFollow-up lookup
AccountPublic Account IDUse resource_url while the account is readable.
Portal userNumeric account-scoped user ID from the account users APINo exact-object GET currently exists, so resource_url is null.
RequestNumeric Request IDUse resource_url for non-deleted Request events. Historical deliveries may still contain null.
TaskPublic Task IDUse resource_url. task_board_id contains the public Task Board ID.
Task attachmentNumeric Task attachment IDUse resource_url to request a fresh signed download URL. The parent fields identify the Task.
Task or file commentNumeric comment IDNo exact-comment GET currently exists. Use the parent fields for context.
Status-update commentPublic string comment IDUse resource_url when present. The parent identifies the status update.
FileNumeric public file IDUse resource_url while the file is readable. For file.version.uploaded, this is the file ID, not a version ID.
DeliverablePublic Deliverable IDNo public read route currently exists, so resource_url is null.
Service requestPublic ID of the resulting TaskUse resource_url. task_board_id contains the public Task Board ID.
Task Board status updatePublic status-update IDUse resource_url when present. Earlier events can keep null.
Live Chat messageTalkJS message IDNo 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 familyapp_url destination
Account created or restored; portal-user eventsAccount overview
Request events except deletionRequest view
Task lifecycle/update events except deletion; Task comments and attachments; service eventsExact Task on its Task Board
File uploads, versions, renames, moves, and commentsFile viewer with the relevant panel open; file comments also select the exact comment
Deliverable published or viewedExact Deliverable
Task Board status update or reactionExact Board overview and update
Status-update comment or comment reactionExact status update with the comment selected
Live Chat messageExact conversation, including accountless conversations
Deleted or removed Account, Request, Task, file, or Deliverablenull

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.

Resourcestatus values
Task lifecycle and update eventsopen or complete
service.requested and service.ticket.createdopen
Requestdraft, scheduled, published, or completed
Task Board status-update-created eventON_TRACK, AT_RISK, OFF_TRACK, ON_HOLD, COMPLETE, or DROPPED
Reaction-added eventnull
Account, portal user, comment, Task attachment, file, Deliverable, and Live Chat messagenull

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:

  1. Select one or more event types.
  2. Send events for every client account or only selected accounts.
  3. Send activity from all people, team members only, or portal users only.
  4. 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.completed sets the field ID and type. It also sets changes to ["requestFieldValue"]. This matches the public API property name. When Include authored text and resource titles is enabled, request_field_label and request_field_value contain the non-sensitive answer context. The object_id remains the Request ID.
  • task.updated adds one or more public Task property names to changes. If you use TypeScript, keep this field typed as string[] so new public properties remain forward compatible.
  • Task objects include task_board_id and an exact resource_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, and portalUser.accessed can include email when Include portal user email addresses is enabled for the endpoint.
  • service.requested identifies the Task created when a portal user requests a service that is not yet active.
  • service.ticket.created identifies 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 in object_id, and the public Task Board ID in task_board_id.
  • liveChat.message.sent identifies the TalkJS message in object_id and 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 typeSent when
account.createdA client account is created.
account.archivedA client account is moved to the archive.
account.restoredAn archived client account is restored.
account.deletedA 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 typeSent when
portalUser.addedA portal user gains access to a client account.
portalUser.removedA portal user’s access to a client account is removed.
portalUser.accessedA 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 resourceWebhook payload
request.idobject_id
request.accountIdaccount_id
field.idrequest_field_id
field.typerequest_field_type
field.labelrequest_field_label
currentAnswer.requestFieldValuerequest_field_value
currentAnswer.actor.idactor_id
currentAnswer.actor.kindactor_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 typeSent when
request.publishedA Request is published for portal users.
request.unpublishedA published Request returns to an unpublished state.
request.completedA portal user submits a Request, or a team member marks it complete.
request.field.completedA portal user saves a response that completes a field. Sydnee sends this once. Later edits do not repeat it.
request.reopenedA completed Request is reopened.
request.archivedA Request is moved to the archive.
request.unarchivedAn archived Request is restored.
request.deletedA 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 typeSent when
task.createdA Task is created.
task.updatedA supported Task detail or link changes. Sydnee queues the event when it saves the change. It does not add a wait.
task.completedA Task is completed. Sydnee schedules the event 45 seconds after the action. The visible Undo window lasts eight seconds.
task.reopenedA completed Task is reopened. Sydnee schedules the event 45 seconds after the action. The visible Undo window lasts eight seconds.
task.archivedA Task is moved to the archive.
task.unarchivedAn archived Task is restored.
task.deletedA Task is deleted.
task.attachment.addedAn 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}/download

task.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 typeParent typeParent IDSent when
task.comment.createdtaskPublic Task ID; also sets task_board_idA comment is added to a Task.
file.comment.createdfileNumeric public file IDA top-level comment or reply is added to a file.
taskBoard.statusUpdate.comment.createdtaskBoardStatusUpdatePublic string status-update ID; also sets task_board_idA 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}/comments

Status-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 typeObject typeObject IDParent
file.comment.reaction.addedcommentNumeric file-comment IDfile and numeric file ID
task.comment.reaction.addedcommentNumeric Task-comment IDtask and public Task ID
taskBoard.statusUpdate.reaction.addedtaskBoardStatusUpdatePublic status-update IDBoth fields null
taskBoard.statusUpdate.comment.reaction.addedcommentPublic string status-comment IDtaskBoardStatusUpdate 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 typeSent when
file.uploadedA new file is uploaded.
file.version.uploadedA new version is uploaded for an existing file.
file.renamedA file’s name changes.
file.movedA file moves to another folder.
file.deletedA 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 typeSent when
deliverable.publishedA folder is published as a Deliverable for portal users.
deliverable.viewedA portal user opens a published Deliverable for the first time.
deliverable.removedA 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 typeObject typeSent when
service.requestedtaskA portal user requests a service that is not yet active.
service.ticket.createdtaskA portal user creates a ticket for an active service.
taskBoard.statusUpdate.createdtaskBoardStatusUpdateA 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 typeSent when
liveChat.message.sentA 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.

HeaderValue
X-Sydnee-Webhook-TimestampUnix time in seconds used for the signature.
X-Sydnee-SignatureCurrent signature in v1=hex_digest form.
X-Sydnee-Signature-PreviousOptional previous-secret signature during the 24-hour rotation overlap.
X-Sydnee-Event-IdStable event ID.
X-Sydnee-Delivery-IdDelivery ID for this endpoint.
X-Sydnee-Attempt-IdUnique 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.

  1. Verify the signature against the raw body.
  2. Store id with a unique constraint.
  3. Return a 2xx response after the event is stored.
  4. Process slower work from your own queue.
  5. Ignore an event whose id was 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.verification in delivery history. Its X-Sydnee-Event-Id starts with verification_.
  • A test delivery uses webhook.test in delivery history. Its X-Sydnee-Event-Id starts with test_.
  • Return 2xx for 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.

Use webhook data with the API