Seasalt.ai Webhook Notification API Tutorial
Overview
A webhook-based notification system that lets your services receive real-time updates from Seasalt.ai products like SeaChat. Whether you want to track new conversations, missed calls, or contact changes, using the following endpoints ensures you stay informed the moment it happens. With support for multiple event types and delivery modes (immediate, delayed, batched), SeaX’s Webhook Notification are ideal for diverse automation and integration needs.
One powerful use case built on top of this webhook system is our Zapier integration. When an event is emitted via webhook, it can immediately trigger a Zap, allowing users to seamlessly connect Seasalt.ai products with tools like Google Sheets, Notion, Slack, and more. In this setup, webhooks act as the upstream event source, and Zapier serves as the downstream automation engine, consuming those events and turning them into custom workflows without any code. Explore the integration here: Seasalt.ai on Zapier
Prerequisites
Ensure the following are in place:
1. Generate Your API Key
All APIs require a valid API key issued from your workspace. All requests must
include a valid API key in the request header (X-API-Key).
Go to Workspace → API Key tab.
Click Add New Key and check
Workspace Events Notificationas the scope.Copy the key and keep it safe. This key is required in the
X-API-KEYheader for all requests.
2. Prepare Your Webhook Receiver
Your server must:
Be publicly accessible over HTTPS
Accept POST requests
Handle application/json payloads
Verify the
Seasalt-Signatureheader on every request, before trusting the payload:# Start in log-only mode (see "Rollout" below) before you ship this rejection. if not verify_seasalt_signature(raw_body, request.headers["Seasalt-Signature"], secret): return Response(status_code=401)Full verification code (Python and Node), a known-answer test vector, and the exact-bytes rule that trips up most integrations are in Verifying Webhook Signatures.
Subscription APIs
This section explains how to manage webhook subscriptions in your workspace.
Create a Subscription
Create a new webhook subscription in your SeaX workspace.
Endpoint
POST https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription
Use this endpoint to register a webhook that will receive event notifications from SeaX.
Authorization
You must provide your API key in the X-API-KEY header.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
webhook_url | string | ✅ | The publicly accessible URL to receive webhook events. |
event_types | array of string | ✅ | List of event types to subscribe to. See supported event types below. |
created_by | string | Identifier of the user creating the subscription. | |
is_enabled | boolean | ✅ | Whether the subscription is active (true) or paused (false). |
type | string | Subscription source. Options: "SEASALT" (default), "ZAPIER" |
Supported Event Types
To get a list of supported event types, you can use this endpoint.
Once you’ve subscribed to the desired event_types, refer to the Event Payload Schema Reference section for detailed information on the structure of each event’s webhook payload. This helps you understand what fields to expect and how to process them correctly in your application.
conversation.newconversation.updatedmessage.newconversation.label.addedconversation.label.deletedcontact.label.addedcontact.label.deletedconversation.endedcall.newcall.updatedcall.endedmeeting.ended
Sample Request
curl -X POST "https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription" \
-H "X-API-KEY: <your_api_key>" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://api.example.com/webhook",
"event_types": ["conversation.new", "message.new"],
"created_by": "user_12345",
"is_enabled": true,
"type": "SEASALT"
}'
Sample Successful Response
{
"webhook_url": "https://api.example.com/webhook",
"event_types": [
"conversation.new",
"message.new"
],
"created_by": "user_12345",
"is_enabled": true,
"type": "SEASALT",
"id": "sub_12345",
"created_at": "2024-03-10T15:30:00Z",
"updated_at": "2024-03-10T15:30:00Z",
"updated_by": "user_12345",
"signing_secret_last_four": "beef",
"secret_updated_at": null,
"rotation_overlap_expires_in_hours": null,
"signing_secret": "seasalt_whsec_1a2b3c4d5e6f7890a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2beef"
}
secret_updated_at and rotation_overlap_expires_in_hours are null here
because no rotation has happened yet — see
Rotate the Signing Secret.
signing_secret is returned only here and on secret rotation — save it
now. GET and list responses never include it (they return
signing_secret_last_four instead), and there is no way to retrieve it again
later. See Verifying Webhook Signatures.
Retrieve a Subscription
This endpoint retrieves the details of a specific webhook subscription by its ID.
Endpoint
GET https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription/{subscription_id}
Authorization
You must provide your API key in the X-API-KEY header.
Sample Request
curl -X GET "https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription/{subscription_id}" \
-H "X-API-KEY: <your_api_key>"
Sample Successful Response
{
"webhook_url": "https://api.example.com/webhook",
"event_types": [
"conversation.new",
"message.new"
],
"created_by": "user_12345",
"is_enabled": true,
"type": "SEASALT",
"id": "sub_12345",
"created_at": "2024-03-10T15:30:00Z",
"updated_at": "2024-03-10T15:30:00Z",
"updated_by": "user_12345",
"signing_secret_last_four": "beef",
"secret_updated_at": null,
"rotation_overlap_expires_in_hours": null
}
This endpoint never returns signing_secret — only signing_secret_last_four.
If you need the full secret again, rotate it.
Retrieve a List of Subscriptions based on criterion
Endpoint
GET https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription
Authorization
This endpoint requires an API key passed in the X-API-KEY header.
Query Parameter
We support the following optional queries to retrieve subscriptions. If not provided, all subscriptions for the workspace will be returned.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
order_by | string | created_at:desc | Order items by field and direction. Format: field:direction | |
type | string | Filter by subscription type. Enum: SEASALT, ZAPIER |
Order By Options
The order_by query parameter allows you to sort the delivery logs by a
specific field and direction.
The format is field:direction, where direction can be either asc for
ascending or desc for descending.
Supported fields for Order By:
order_by Value | Description |
|---|---|
created_at:asc | Oldest subscriptions first |
created_at:desc | Newest subscriptions first (default) |
updated_at:asc | Least recently updated subscriptions first |
updated_at:desc | Most recently updated subscriptions first |
id:asc | Sort by subscription ID (A–Z) |
id:desc | Sort by subscription ID (Z–A) |
created_by:asc | Creator A–Z |
created_by:desc | Creator Z–A |
updated_by:asc | Last updater A–Z |
updated_by:desc | Last updater Z–A |
is_enabled:asc | Inactive subscriptions first |
is_enabled:desc | Active subscriptions first |
type:asc | Subscription type A–Z (SEASALT, ZAPIER) |
type:desc | Subscription type Z–A |
If not specified, the default is created_at:desc.
Sample Request
curl -X GET "https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription?order_by=created_at:asc&type=SEASALT" \
-H "X-API-KEY: <your_api_key>"
Sample Successful Response
{
"subscriptions": [
{
"webhook_url": "https://api.example.com/webhook",
"event_types": [
"conversation.new",
"message.new"
],
"created_by": "user_12345",
"is_enabled": true,
"type": "SEASALT",
"id": "sub_12345",
"created_at": "2024-03-10T15:30:00Z",
"updated_at": "2024-03-10T15:30:00Z",
"updated_by": "user_12345"
}
]
}
Update a Subscription
Modify an existing webhook subscription to update its webhook URL, event types, status, or type.
Endpoint
PATCH https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription/{subscription_id}
Authorization
This endpoint requires an API key passed in the X-API-KEY header.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| webhook_url | string | No | The URL to which webhook events will be delivered. |
| event_types | string[] | No | A list of event types you want to subscribe to. |
| is_enabled | boolean | No | Whether the subscription is active. |
| type | string | No | Type of subscription. Enum: SEASALT, ZAPIER. |
| updated_by | string | Yes | Email or identifier of the user making the update. |
Sample Request
curl -X PATCH "https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription/{subscription_id}" \
-H "X-API-KEY: <your_api_key>" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://api.example.com/webhook",
"event_types": ["conversation.new", "message.new"],
"updated_by": "user_12345",
"is_enabled": true
}'
Sample Successful Response
{
"webhook_url": "https://api.example.com/webhook",
"event_types": [
"conversation.new",
"message.new"
],
"created_by": "user_12345",
"is_enabled": true,
"type": "SEASALT",
"id": "sub_12345",
"created_at": "2024-03-10T15:30:00Z",
"updated_at": "2024-03-10T15:30:00Z",
"updated_by": "user_12345"
}
Rotate the Signing Secret
Generate a new signing secret for a subscription without any delivery downtime. The previous secret keeps signing — and verifying — for an overlap window you choose, so in-flight deliveries and a receiver that hasn’t redeployed yet both keep working while you roll the new secret out.
Endpoint
POST https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription/{subscription_id}/rotate-secret
Authorization
You must provide your API key in the X-API-KEY header.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
overlap_hours | integer | How many hours the previous secret keeps signing (and verifying) after rotation. Default 24, min 1, max 168 (7 days). Values outside this range return the standard error envelope. |
Sample Request
curl -X POST "https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription/{subscription_id}/rotate-secret" \
-H "X-API-KEY: <your_api_key>" \
-H "Content-Type: application/json" \
-d '{
"overlap_hours": 24
}'
Sample Successful Response
{
"webhook_url": "https://api.example.com/webhook",
"event_types": [
"conversation.new",
"message.new"
],
"created_by": "user_12345",
"is_enabled": true,
"type": "SEASALT",
"id": "sub_12345",
"created_at": "2024-03-10T15:30:00Z",
"updated_at": "2024-03-10T15:30:00Z",
"updated_by": "user_12345",
"signing_secret_last_four": "beef",
"secret_updated_at": "2024-06-01T09:00:00Z",
"rotation_overlap_expires_in_hours": 24,
"signing_secret": "seasalt_whsec_9f8e7d6c5b4a3928170615243342515061708090a1b2c3d4e5f6a7b8c9d0beef"
}
Just like the create response, signing_secret here is the full new secret
and is returned only this once — store it now. While
rotation_overlap_expires_in_hours counts down, deliveries carry two v1=
signatures (one per secret) in Seasalt-Signature, and a receiver that
accepts a match against either keeps working without any code change. See
Verifying Webhook Signatures for the full
rotation semantics and receiver-side handling.
Confirm Secret Rotation
Immediately retire the previous signing secret instead of waiting for the overlap window to expire. Call this once you’ve deployed the new secret to your receiver — or right away, if you rotated because the old secret may have been compromised.
Endpoint
POST https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription/{subscription_id}/confirm-rotation
Authorization
You must provide your API key in the X-API-KEY header.
This endpoint takes no request body. Calling it when no rotation is in flight is a no-op success, not an error — safe to call defensively.
Sample Request
curl -X POST "https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription/{subscription_id}/confirm-rotation" \
-H "X-API-KEY: <your_api_key>"
Sample Successful Response
{
"webhook_url": "https://api.example.com/webhook",
"event_types": [
"conversation.new",
"message.new"
],
"created_by": "user_12345",
"is_enabled": true,
"type": "SEASALT",
"id": "sub_12345",
"created_at": "2024-03-10T15:30:00Z",
"updated_at": "2024-03-10T15:30:00Z",
"updated_by": "user_12345",
"signing_secret_last_four": "beef",
"secret_updated_at": "2024-06-01T09:00:00Z",
"rotation_overlap_expires_in_hours": null
}
rotation_overlap_expires_in_hours returns to null once confirmed — the
previous secret is retired for good and only the current one verifies. This
response never includes signing_secret; if you need the full value again,
rotate.
Remove a Subscription
Delete an existing webhook subscription from your workspace. This action permanently disables event delivery to the specified webhook URL.
Endpoint
DELETE https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription/{subscription_id}
Authorization
This endpoint requires an API key passed in the X-API-KEY header.
Sample Request
curl -X DELETE "https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription/{subscription_id}" \
-H "X-API-KEY: <your_api_key>"
Sample Request
HTTP Status Code: 204 No Content
Get a List of Supported Events
Retrieve a list of all event types that can be used when creating or updating webhook subscriptions. Each event type includes a brief description of when it is triggered.
Endpoint
GET https://seax.seasalt.ai/notify-api/v1/event_types
Authorization
This endpoint requires an API key passed in the X-API-KEY header.
Sample Request
curl -X GET "https://seax.seasalt.ai/notify-api/v1/event_types" \
-H "X-API-KEY: <your_api_key>"
Sample Successful Response
[
{
event_type: 'conversation.new',
description: 'Triggered when a new conversation is created',
},
{
event_type: 'conversation.updated',
description: 'Triggered when a conversation is updated',
},
{
event_type: 'message.new',
description: 'Triggered when a message is sent',
},
{
event_type: 'conversation.ended',
description: 'Triggered when a conversation is ended',
},
{
event_type: 'conversation.label.added',
description: 'Triggered when a label is added to a conversation',
},
{
event_type: 'conversation.label.deleted',
description: 'Triggered when a label is removed from a conversation',
},
{
event_type: 'contact.label.added',
description: 'Triggered when a label is added to a contact',
},
{
event_type: 'contact.label.deleted',
description: 'Triggered when a label is removed from a contact',
},
{
event_type: 'call.new',
description: 'Triggered when a call starts',
},
{
event_type: 'call.ended',
description: 'Triggered when a call ends',
},
{
event_type: 'call.updated',
description: 'Triggered when a call summary is generated',
},
{
"event_type": "meeting.ended",
"description": "Triggered when a meeting ends"
}
];
Test Your Webhook and Know What Will Be Sent
Simulate different types of events to validate your webhook endpoint. This section walks you through:
- How test requests are constructed
- What payload structure to expect
Endpoint
POST https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/test
Authorization
This endpoint requires an API key passed in the X-API-KEY header.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| event_type | string | Yes | The type of event to simulate (e.g. conversation.new) |
| webhook_url | string | Yes | The URL to which the test payload will be sent |
| subscription_id | string | No | If supplied, the test delivery is signed exactly like a real webhook event, using that subscription’s active signing secret(s), and the response includes signature_debug. Omit for the original, unsigned behavior. |
Sample Request
curl -X POST "https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/test" \
-H "X-API-KEY: <your_api_key>" \
-H "Content-Type: application/json" \
-d '{
"event_type": "conversation.new",
"webhook_url": "https://api.example.com/test-webhook",
"subscription_id": "sub_12345"
}'
Sample Successful Response
The actual response body sent to your webhook_url depends on the event_type
provided. Below is an example response for the conversation.new event type:
{
"id": "6e74c661-4c66-4d1e-81b0-64b2f4dcac98",
"affect": "add",
"version": "0.0.1",
"timestamp": "2025-06-20T23:44:30.000000",
"event_type": "conversation.new",
"workspace": {
"id": "workspace-123",
"name": "Test Workspace"
},
"source": {
"id": "source-456",
"type": "WEBCHAT",
"identifier": "Test AI Agent"
},
"data": {
"conversation_id": "conv-789",
"conversation_title": "Example Conversation",
"bot_id": "bot-001",
"conversation_config_id": "cfg-001",
"channel": "WEBCHAT",
"customer": {
"id": "cust-001",
"name": "Test User",
"email": "Test@example.com",
"phone": "+123456789",
"address": "Test AI Agent",
"channel": "WEBCHAT"
},
"latest_inbound_message": {
"id": "msg-in-123456",
"direction": "INBOUND",
"text": "Hello, I need help!",
"type": "text",
"created_at": "2025-06-20T23:44:00.000000"
},
"latest_outbound_message": {
"id": "msg-out-123456",
"direction": "OUTBOUND",
"text": "Sure, how can I assist?",
"type": "text",
"created_at": "2025-06-20T23:44:30.000000"
}
},
"subscription_created_by": "Test_user",
"subscription_updated_by": "Test_user"
}
Debugging a failing verification with signature_debug
When the request includes subscription_id, the response is extended with a
signature_debug object describing exactly what SeaNotify signed and sent —
so you can diff it against your own computation instead of guessing:
{
"status": "success",
"status_code": 200,
"response_body": { ... },
"signature_debug": {
"signed_body": "{\"event_type\": \"conversation.new\", ...}",
"timestamp": 1788375146,
"expected_signature": "t=1788375146,v1=5257a869e7ec...",
"secrets_used": ["beef"]
}
}
| Field | Type | Description |
|---|---|---|
signed_body | string | The exact raw bytes (as text) that were signed and sent as the request body. Diff this against what your own code hashes to catch a “verifying the parsed body instead of raw bytes” mismatch. |
timestamp | integer | The Unix epoch timestamp used in the signature (the t= field of Seasalt-Signature). Compare against your own clock to rule out skew. |
expected_signature | string | The exact Seasalt-Signature header value this test delivery computed and sent. |
secrets_used | array of string | The last four characters of each secret that contributed a v1= value, in the same order as expected_signature (current secret first) — two entries while a rotation overlap is active, one otherwise. The full secret is never included. |
signature_debug is omitted entirely (not sent as null) when the request
didn’t include subscription_id.
If your receiver is rejecting signatures and you can’t tell why, use this table to narrow it down:
| Symptom | Likely cause |
|---|---|
| Every delivery fails | Verifying the parsed/re-serialized body instead of the raw bytes |
| Intermittent failures | Receiver clock skew beyond the 300-second tolerance |
| Started failing suddenly, was fine before | Rotation overlap expired — deploy the new secret |
| Fails only for some event types | Body contains non-ASCII characters and the receiver re-encoded it |
See Verifying Webhook Signatures for the full verification code and the rules behind each of these.
Delivery Logs
Learn how to retrieve delivery logs for your webhooks and what you can expect in logs.
Get Webhook Delivery Logs for a Workspace
Retrieves webhook delivery logs for a specific workspace. You can optionally filter results by event type, delivery status, date range, and more.
Endpoint
GET https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/logs
Authorization
This endpoint requires an API key passed in the X-API-KEY header.
Query Parameter
The following optional query parameters are supported.
| Name | Type | Description |
|---|---|---|
event_type | string | Filter logs by event type (e.g. conversation.new) |
delivery_status | string | Filter by delivery status (success, failed) |
start_date | datetime | Return logs created on or after this date (ISO 8601 format). Example: 2024-06-01T23:59:59-07:00 (America/Los_Angeles), 2024-06-01T23:59:59+08:00 (Asia/Singapore) |
end_date | datetime | Return logs created on or before this date. Example: 2024-06-01T23:59:59-07:00 (America/Los_Angeles), 2024-06-01T23:59:59+08:00 (Asia/Singapore) |
order_by | string | Sort results (default: created_at:desc). Format: field:direction |
limit | integer | Maximum number of results (default: 0 for unlimited) |
offset | integer | Number of results to skip (default: 0) |
Order By Options
The order_by query parameter allows you to sort the delivery logs by a
specific field and direction.
The format is field:direction, where direction can be either asc for
ascending or desc for descending.
Supported fields for Order By:
order_by Value | Description |
|---|---|
created_at:asc | Oldest logs first |
created_at:desc | Newest logs first (default) |
event_type:asc | Event type A–Z |
event_type:desc | Event type Z–A |
delivery_status:asc | Status A–Z (e.g. failed before success) |
delivery_status:desc | Status Z–A |
status_code:asc | Lower HTTP status codes first (e.g. 200 → 500) |
status_code:desc | Higher HTTP status codes first (e.g. 500 → 200) |
If not specified, the default is created_at:desc.
Sample Request
curl -X GET "https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/logs?event_type=conversation.new&delivery_status=success&order_by=created_at:desc&limit=10&offset=0" \
-H "X-API-KEY: <your_api_key>"
Sample Successful Response
{
"total": 1,
"data": [
{
"id": "1234567890abcdef",
"workspace_id": "ws_123456",
"event_id": "evt_987654321",
"subscription_id": "29efe26f-eb8a-4ee8-9abf-d8651a31283e",
"event_type": "conversation.new",
"webhook_url": "https://api.example.com/webhooks/incoming",
"delivery_status": "success",
"status_code": "200",
"response_body": "{\"status\": \"received\", \"message\": \"Webhook processed successfully\"}",
"delivered_event_obj": {
"id": "1246965",
"workspace_id": "1246965",
"workspace_name": "seasalt.ai bot",
"value": {
"event_type": "conversation.new",
"event_source": "seax",
"event_triggered_by": "kelly@seasalt.ai",
"payload": {
"conversation": {
"id": "conv_123",
"title": "New Conversation"
}
},
"occurred_at": "2025-06-20T23:44:30.000000",
"sent_at": "2025-06-20T23:44:30.000000",
"subscription_created_by": "kelly@seasalt.ai",
"subscription_updated_by": "kelly@seasalt.ai"
}
},
"created_at": "2024-03-11T04:18:13.558258"
}
]
}
Get Webhook Delivery Logs for a Subscription
Retrieve webhook delivery logs tied to a specific subscription. Supports optional filtering by event type, delivery status, date range, ordering, and pagination.
Endpoint
GET https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/logs/{subscription_id}
Authorization
This endpoint requires an API key passed in the X-API-KEY header.
| Name | Type | Description |
|---|---|---|
event_type | string | Filter logs by event type (e.g. conversation.new) |
delivery_status | string | Filter by delivery status (success, failed) |
start_date | datetime | Return logs created on or after this date (ISO 8601 format). Example: 2024-06-01T23:59:59-07:00 (America/Los_Angeles), 2024-06-01T23:59:59+08:00 (Asia/Singapore) |
end_date | datetime | Return logs created on or before this date. Example: 2024-06-01T23:59:59-07:00 (America/Los_Angeles), 2024-06-01T23:59:59+08:00 (Asia/Singapore) |
order_by | string | Sort results (default: created_at:desc). Format: field:direction |
limit | integer | Maximum number of results (default: 0 for unlimited) |
offset | integer | Number of results to skip (default: 0) |
Order By Options
The order_by query parameter allows you to sort the delivery logs by a
specific field and direction.
The format is field:direction, where direction can be either asc for
ascending or desc for descending.
Supported fields for ordering:
order_by Value | Description |
|---|---|
created_at:asc | Oldest logs first |
created_at:desc | Newest logs first (default) |
event_type:asc | Event type A–Z |
event_type:desc | Event type Z–A |
delivery_status:asc | Status A–Z (e.g. failed before success) |
delivery_status:desc | Status Z–A |
status_code:asc | Lower HTTP status codes first (e.g. 200 → 500) |
status_code:desc | Higher HTTP status codes first (e.g. 500 → 200) |
If not specified, the default is created_at:desc.
Sample Request
curl -X GET "https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/logs/{subscription_id}?event_type=conversation.new&delivery_status=success&order_by=created_at:desc&limit=10&offset=0" \
-H "X-API-KEY: <your_api_key>"
Sample Request
{
"total": 1,
"data": [
{
"id": "1234567890abcdef",
"workspace_id": "ws_123456",
"event_id": "evt_987654321",
"subscription_id": "29efe26f-eb8a-4ee8-9abf-d8651a31283e",
"event_type": "conversation.new",
"webhook_url": "https://api.example.com/webhooks/incoming",
"delivery_status": "success",
"status_code": "200",
"response_body": "{\"status\": \"received\", \"message\": \"Webhook processed successfully\"}",
"delivered_event_obj": {
"id": "1246965",
"workspace_id": "1246965",
"workspace_name": "seasalt.ai bot",
"value": {
"event_type": "conversation.new",
"event_source": "seax",
"event_triggered_by": "kelly@seasalt.ai",
"payload": {
"conversation": {
"id": "conv_123",
"title": "New Conversation"
}
},
"occurred_at": "2025-06-20T23:44:30.000000",
"sent_at": "2025-06-20T23:44:30.000000",
"subscription_created_by": "kelly@seasalt.ai",
"subscription_updated_by": "kelly@seasalt.ai"
}
},
"created_at": "2024-03-11T04:18:13.558258"
}
]
}
Export Webhook Delivery Logs to Email
Export webhook delivery logs for a workspace within a specific date range and receive a download link via email.
Endpoint
POST https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/logs/export
Authorization
This endpoint requires an API key passed in the X-API-KEY header.
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
email | string | ✅ | The email address to receive the download link. |
start_date | datetime | Start date of the logs to export (ISO 8601 format). Example: 2024-06-01T23:59:59-07:00 (America/Los_Angeles), 2024-06-01T23:59:59+08:00 (Asia/Singapore) | |
end_date | datetime | End date of the logs to export (ISO 8601 format). Example: 2024-06-01T23:59:59-07:00 (America/Los_Angeles), 2024-06-01T23:59:59+08:00 (Asia/Singapore) | |
lang | string | Either en-US or zh-TW. We currently support two languages in the email template: English and Traditional Chinese. If any other value is provided, the template will default to English. |
Sample Request
curl -X POST "https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/logs/export" \
-H "X-API-KEY: <your_api_key>" \
-H "Content-Type: application/json" \
-d '{
"start_date": "2024-03-01T00:00:00Z",
"end_date": "2024-03-31T23:59:59Z",
"lang": "en-US",
"email": "test@email.com"
}'
Sample Successful Response
{
"job_id": "export_job_abc123",
"message": "Log export job started. You will receive an email once the file is ready."
}
Result
You will receive an email containing a secure download link to the exported log file once the export is complete.
Notes
This is an asynchronous export. Processing time varies depending on the data volume.
Verifying Webhook Signatures
Every outbound webhook — including /test deliveries made with a
subscription_id — is signed with HMAC-SHA256 so you can confirm it really
came from SeaNotify and was not modified in transit. Verification happens
entirely on your side; nothing below is required to receive events, only to
trust them. Your signing secret comes from the signing_secret field in the
POST https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription
response — shown once, at creation (and again on rotation — see
Rotate the Signing Secret).
Headers added to every delivery:
| Header | Example | Meaning |
|---|---|---|
Seasalt-Signature | t=1788375146,v1=5257a869e7ec... | Comma-separated: t is the Unix timestamp (UTC seconds) at signing time, v1 is the hex-encoded HMAC-SHA256 signature. May carry more than one v1= during a secret rotation — see “Rotating your signing secret” below. |
Seasalt-Event-Id | 9b1d2e3f-... | The event’s ID, generated once per event — every subscription notified for the same event receives the same value (useful for correlating a fan-out). Convenience metadata; not covered by the signature. |
Seasalt-Event-Type | message.new | Convenience copy of the event type already present in the body. Not covered by the signature. |
Seasalt-Subscription-Id | sub_... | Identifies which subscription’s secret to verify against. Not covered by the signature. |
Only t and the request body are covered by Seasalt-Signature. The
other three headers are attached to the request after signing and are not
authenticated — do not treat any of them as the sole basis of a security
decision. See “Replay protection and idempotency” below for what this means
in practice.
These headers are unprefixed (Seasalt-Signature, not X-Seasalt-Signature)
to match the convention used by most webhook providers today (RFC 6648
deprecated the X- prefix in 2012). The existing X-API-Key header keeps its
established name — both shapes exist in this API by design.
The signing key is the full secret string as displayed, including the
seasalt_whsec_ prefix. Stripping the prefix before using it as the HMAC key is a
common mistake and will produce a signature that never matches.
Copy-paste verification code for Python and Node is below.
Verify in Python
import hashlib
import hmac
import time
TOLERANCE_SECONDS = 300
def verify_seasalt_signature(raw_body: bytes, header: str, secret: str) -> bool:
"""Verify a SeaNotify webhook signature.
raw_body MUST be the raw request bytes, read BEFORE any JSON parsing.
secret is the full value shown at creation, including the `seasalt_whsec_` prefix.
"""
timestamp = None
signatures = []
for part in header.split(","):
key, _, value = part.strip().partition("=")
if key == "t":
timestamp = value
elif key == "v1":
signatures.append(value)
if timestamp is None or not signatures:
return False
try:
timestamp = int(timestamp)
except ValueError:
return False
# Reject replays and badly-skewed clocks. Compare two ints, never a float:
# an attacker-sized `t` (thousands of digits) parses fine as a Python int,
# but time.time() is a float, and float(huge_int) raises OverflowError ---
# turning a forged header into a 500 instead of False.
if abs(int(time.time()) - timestamp) > TOLERANCE_SECONDS:
return False
signed_payload = f"{timestamp}.".encode("utf-8") + raw_body
expected = hmac.new(secret.encode("utf-8"), signed_payload, hashlib.sha256).hexdigest()
# Compare as bytes, not str: hmac.compare_digest raises TypeError if a str
# argument contains non-ASCII characters, and v1= is attacker-controlled.
# During a secret rotation we send several v1= values. Accept if ANY matches.
expected_bytes = expected.encode("utf-8")
return any(hmac.compare_digest(expected_bytes, sig.encode("utf-8")) for sig in signatures)
# Rollout: run this in log-only mode first --- compare and log, but still return 200.
# Switch to rejecting on False only once your logs are clean.
Verify in Node
const crypto = require("crypto");
const TOLERANCE_SECONDS = 300;
// Rollout: run this in log-only mode first --- compare and log, but still return 200.
// Switch to rejecting on false only once your logs are clean.
function verifySeasaltSignature(rawBody, header, secret) {
// rawBody MUST be a Buffer of the raw request body, captured before JSON parsing.
let timestamp = null;
const signatures = [];
for (const part of header.split(",")) {
const idx = part.indexOf("=");
if (idx === -1) continue;
const key = part.slice(0, idx).trim();
const value = part.slice(idx + 1).trim();
if (key === "t") timestamp = value;
else if (key === "v1") signatures.push(value);
}
if (timestamp === null || signatures.length === 0) return false;
if (!/^\d+$/.test(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) return false;
const signedPayload = Buffer.concat([Buffer.from(`${timestamp}.`, "utf8"), rawBody]);
const expected = crypto.createHmac("sha256", secret).update(signedPayload).digest("hex");
const expectedBuf = Buffer.from(expected, "utf8");
return signatures.some((sig) => {
const sigBuf = Buffer.from(sig, "utf8");
// timingSafeEqual THROWS on length mismatch --- guard before calling it.
return sigBuf.length === expectedBuf.length && crypto.timingSafeEqual(expectedBuf, sigBuf);
});
}
Express’s express.json() consumes the body and leaves no raw bytes to
verify against — the single most common Node integration failure. Mount
express.raw({ type: "application/json" }) on your webhook route (or
capture the raw body with a verify: callback) before this function ever
runs.
Known-answer test vector
Run both snippets against this vector before going live — they should produce this exact signature.
secret = seasalt_whsec_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
timestamp = 1788375146
raw_body = {"event_type": "message.new", "conversation_id": "conv_123", "timestamp": "2026-09-02T10:52:26"}
signature = bc171327eb40c058d2d73d6028c9636a9a1e74c858f3c468c15c1a8149ebf247
header = Seasalt-Signature: t=1788375146,v1=bc171327eb40c058d2d73d6028c9636a9a1e74c858f3c468c15c1a8149ebf247
Note the spaces after : and , in raw_body — that is not a typo. It is
the default output of json.dumps, which is exactly what SeaNotify transmits.
If your test vector doesn’t match, check whether your code is re-serializing
the body before comparing.
The exact-bytes rule
Always verify against the raw request body, before any JSON parsing.
Compute the signature over the exact bytes you received on the wire — not
over json.dumps(json.loads(raw_body)), not over a re-formatted or
pretty-printed copy. Re-serializing changes key order, whitespace, and
unicode escaping, all of which change the bytes and therefore the signature,
even though the parsed object is identical. This is the single most common
webhook-signature bug across every provider, not just SeaNotify’s.
Constant-time comparison
Compare signatures with a constant-time function — hmac.compare_digest in
Python, crypto.timingSafeEqual in Node — never == or ===. A naive
comparison short-circuits on the first mismatched byte, and the resulting
timing difference is enough to let an attacker recover a valid signature one
byte at a time. Both snippets above already do this correctly; keep it that
way if you adapt them.
One landmine specific to Node: crypto.timingSafeEqual throws if the two
buffers have different lengths, instead of returning false. An unguarded
call turns a forged (wrong-length) signature into an unhandled exception — a
500 — rather than a clean rejection. The Node snippet above guards this with
a length check before calling it; keep that guard if you adapt it.
Rotating your signing secret
Call
POST https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription/{subscription_id}/rotate-secret
(optional body: {"overlap_hours": 24}, max 168) to generate a new
secret. The previous secret keeps signing — and verifying — for the overlap
window, so in-flight deliveries never break mid-rotation. During that window
Seasalt-Signature carries two v1= values, one per secret; the
verification code above already accepts a delivery if any v1 matches,
so no receiver-side change is needed to survive a rotation. See
Rotate the Signing Secret for the full request
and response shape.
Once you’ve deployed the new secret, call
POST https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription/{subscription_id}/confirm-rotation
(no body) to immediately retire the previous one rather than waiting for the
overlap window to expire. This is also the right call if the old secret may
have been compromised — closing the window early beats waiting it out.
GET https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/subscription/{subscription_id}
reports rotation_overlap_expires_in_hours at any time so you can see whether a
rotation is in flight and how much time is left; it is null when none is.
See Confirm Secret Rotation for details.
There is no endpoint to retrieve a lost secret — GET and list responses
only ever return signing_secret_last_four. If you’ve lost it, rotate.
Replay protection and idempotency
Reject any delivery whose t is more than 300 seconds from your own
clock — SeaNotify does not enforce this itself, so pick this tolerance
consistently on your side. This tolerance check, against the signed
timestamp, is your actual replay defense.
Seasalt-Event-Id is convenience metadata, not a security control. It is
added to the request after signing and is not covered by Seasalt-Signature,
so it is fully attacker-controlled on a replayed request: someone who
captures one valid delivery can replay the identical signed body and
signature inside the 300-second window with any Seasalt-Event-Id they
like, and it will still verify. Deduplicating on Seasalt-Event-Id alone
does not stop this. Use it for what it is actually good for — recognizing a
genuine (non-malicious) retry, and correlating a single event’s fan-out
across your subscriptions, since every subscription notified for the same
event receives the same event_id by design — but keep your replay defense
on the signed timestamp above, applied to content you have independently
verified.
Rollout: log-only, then enforce
Don’t let turning on enforcement be how you discover your verification code is wrong — that failure mode drops live production events. Roll out in two phases, exactly as noted in the comment carried in both snippets above:
- Log only. Compute the signature, log whether it matched, and still
return
200regardless of the result. Run until your logs are clean. - Enforce. Once you trust the logs, start rejecting on a mismatch.
Debugging a failing verification
If your receiver is rejecting signatures and you can’t tell why, call
POST https://seax.seasalt.ai/notify-api/v1/workspaces/{workspace_id}/test
with a subscription_id in the body. The response includes a
signature_debug object — the exact body that was signed, the timestamp
used, and the Seasalt-Signature value SeaNotify computed and sent — so you
can diff it against your own computation instead of guessing. See
Test Your Webhook and Know What Will Be Sent
for the full response shape.
| Symptom | Likely cause |
|---|---|
| Every delivery fails | Verifying the parsed/re-serialized body instead of the raw bytes |
| Intermittent failures | Receiver clock skew beyond the 300-second tolerance |
| Started failing suddenly, was fine before | Rotation overlap expired — deploy the new secret |
| Fails only for some event types | Body contains non-ASCII characters and the receiver re-encoded it |
Event Payload Schema Reference
This section provides a detailed breakdown of the payload schema for each supported event type in the Webhook Notification API. Each event includes standard metadata (like id, timestamp, workspace) and a structured data object specific to the event. Use these schema definitions to correctly parse, process, and respond to webhook notifications in your application.
conversation.new
This event is triggered when a new conversation is initiated within the Seasalt.ai platform. It typically includes details about the conversation, customer, and the latest messages exchanged.
Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier of the event |
event_type | string | Always "conversation.new" |
affect | string | Type of change, usually "add" |
version | string | Event schema version, e.g. "0.0.1" |
timestamp | string | When the event occurred, in ISO 8601 format |
workspace.id | string | Workspace ID associated with the event |
workspace.name | string | Workspace name |
source.id | string | ID of the event source (e.g., AI agent ID) |
source.type | string | Channel type (e.g., WEBCHAT, MESSENGER, etc.) |
source.identifier | string | Identifier or name of the bot or integration |
data.conversation_id | string | ID of the new conversation |
data.conversation_title | string | Title of the conversation |
data.bot_id | string (optional) | ID of the bot handling the conversation, or null if none is assigned |
data.conversation_config_id | string (optional) | ID of the conversation’s configuration, or null if none |
data.channel | string | Channel where the conversation started |
data.customer.id | string | Customer ID |
data.customer.name | string | Customer name |
data.customer.email | string | Customer email address |
data.customer.phone | string | Customer phone number |
data.customer.address | string | Address or source identifier |
data.customer.channel | string | Customer’s channel (should match data.channel) |
data.latest_inbound_message.id | string | ID of the latest inbound message |
data.latest_inbound_message.direction | string | Always "INBOUND" |
data.latest_inbound_message.text | string | Message content |
data.latest_inbound_message.type | string | Type of message (e.g. "text") |
data.latest_inbound_message.created_at | string | Timestamp of message creation (ISO 8601) |
data.latest_outbound_message.id | string | ID of the latest outbound message |
data.latest_outbound_message.direction | string | Always "OUTBOUND" |
data.latest_outbound_message.text | string | Message content |
data.latest_outbound_message.type | string | Type of message (e.g. "text") |
data.latest_outbound_message.created_at | string | Timestamp of message creation (ISO 8601) |
Sample Event Payload
{
"id": "6e74c661-4c66-4d1e-81b0-64b2f4dcac98",
"event_type": "conversation.new",
"affect": "add",
"version": "0.0.1",
"timestamp": "2025-06-20T23:45:00.000000",
"workspace": {
"id": "workspace-123",
"name": "Test Workspace"
},
"source": {
"id": "source-456",
"type": "WEBCHAT",
"identifier": "Test AI Agent"
},
"data": {
"conversation_id": "conv-789",
"conversation_title": "Test Example Conversation",
"bot_id": "bot-001",
"conversation_config_id": "cfg-001",
"channel": "WEBCHAT",
"customer": {
"id": "fb0b4af0-ad6a-48a3-ad3c-d10b237b432c",
"name": "Test User",
"email": "Test@example.com",
"phone": "+123456789",
"address": "Test AI Agent",
"channel": "WEBCHAT"
},
"latest_inbound_message": {
"id": "msg-in-123456",
"direction": "INBOUND",
"text": "Hello, I need help!",
"type": "text",
"created_at": "2025-06-20T23:44:00.000000"
},
"latest_outbound_message": {
"id": "msg-out-123456",
"direction": "OUTBOUND",
"text": "Sure, how can I assist?",
"type": "text",
"created_at": "2025-06-20T23:44:30.000000"
}
},
"subscription_created_by": "Test_user",
"subscription_updated_by": "Test_user"
}
conversation.updated
This event is triggered when an existing conversation is updated—such as changes to customer info, title, or other metadata.
Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier of the event |
event_type | string | Always "conversation.updated" |
affect | string | Type of change, typically "change" |
version | string | Event schema version, e.g. "0.0.1" |
timestamp | string | When the event occurred, in ISO 8601 format |
workspace.id | string | Workspace ID associated with the event |
workspace.name | string | Workspace name |
source.id | string | ID of the event source (e.g., AI agent ID) |
source.type | string | Channel type (e.g., WEBCHAT, MESSENGER, etc.) |
source.identifier | string | Identifier or name of the bot or integration |
data.conversation_id | string | ID of the updated conversation |
data.conversation_title | string | Title of the conversation |
data.bot_id | string (optional) | ID of the bot handling the conversation, or null if none is assigned |
data.conversation_config_id | string (optional) | ID of the conversation’s configuration, or null if none |
data.channel | string | Channel where the conversation takes place |
data.customer.id | string | Customer ID |
data.customer.name | string | Customer name |
data.customer.email | string | Customer email address |
data.customer.phone | string | Customer phone number |
data.customer.address | string | Address or source identifier |
data.customer.channel | string | Customer’s channel (should match data.channel) |
data.updated_fields | array of string | List of fields that were updated |
data.previous | object | Previous values before the update |
data.current | object | Current values after the update |
data.updated_by.id | string (optional) | ID of the user or agent who made the update |
data.updated_by.name | string (optional) | Name of the user or agent who made the update |
data.updated_by.type | string (optional) | Type of entity (e.g., agent, system) |
data.updated_at | string | Timestamp of the update (ISO 8601) |
Sample Event Payload
{
"id": "cb1537d3-712e-44cf-8709-11b3fe55177a",
"event_type": "conversation.updated",
"affect": "change",
"version": "0.0.1",
"timestamp": "2025-06-20T23:45:00.000000",
"workspace": {
"id": "workspace-123",
"name": "Test Workspace"
},
"source": {
"id": "source-456",
"type": "WEBCHAT",
"identifier": "Test AI Agent"
},
"data": {
"conversation_id": "conv-789",
"conversation_title": "Test Example Conversation",
"bot_id": "bot-001",
"conversation_config_id": "cfg-001",
"channel": "WEBCHAT",
"customer": {
"id": "fb0b4af0-ad6a-48a3-ad3c-d10b237b432c",
"name": "Test User",
"email": "zapier@example.com",
"phone": "+123456789",
"address": "Test AI Agent",
"channel": "WEBCHAT"
},
"updated_fields": ["is_unread"],
"previous": {
"is_unread": "false"
},
"current": {
"is_unread": "true"
},
"updated_by": {
"id": "user_123",
"name": "Admin",
"type": "agent"
},
"updated_at": "2025-06-20T23:44:59.000000"
},
"subscription_created_by": "Test_user",
"subscription_updated_by": "Test_user"
}
conversation.ended
This event is triggered when a conversation ends or is closed. It shares the same structure as conversation.updated, with the affect field set to “delete”.
Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier of the event |
event_type | string | Always "conversation.ended" |
affect | string | Always "delete" |
version | string | Event schema version, e.g. "0.0.1" |
timestamp | string | When the event occurred, in ISO 8601 format |
workspace.id | string | Workspace ID associated with the event |
workspace.name | string | Workspace name |
source.id | string | ID of the event source (e.g., AI agent ID) |
source.type | string | Channel type (e.g., WEBCHAT, MESSENGER, etc.) |
source.identifier | string | Identifier or name of the bot or integration |
data.conversation_id | string | ID of the ended conversation |
data.conversation_title | string | Title of the conversation |
data.bot_id | string (optional) | ID of the bot handling the conversation, or null if none is assigned |
data.conversation_config_id | string (optional) | ID of the conversation’s configuration, or null if none |
data.channel | string | Channel where the conversation took place |
data.customer.id | string | Customer ID |
data.customer.name | string | Customer name |
data.customer.email | string | Customer email address |
data.customer.phone | string | Customer phone number |
data.customer.address | string | Address or source identifier |
data.customer.channel | string | Customer’s channel (should match data.channel) |
data.updated_fields | array of string | List of fields that were changed before the conversation ended |
data.previous | object | Previous values before the final update |
data.current | object | Final values at the moment the conversation ended |
data.updated_by.id | string (optional) | ID of the user or agent who ended the conversation |
data.updated_by.name | string (optional) | Name of the user or agent |
data.updated_by.type | string (optional) | Type of entity (e.g., agent, system) |
data.updated_at | string | Timestamp when the conversation was ended |
Sample Event Payload
{
"id": "cb1537d3-712e-44cf-8709-11b3fe55177a",
"event_type": "conversation.ended",
"affect": "delete",
"version": "0.0.1",
"timestamp": "2025-06-20T23:45:00.000000",
"workspace": {
"id": "workspace-123",
"name": "Test Workspace"
},
"source": {
"id": "source-456",
"type": "WEBCHAT",
"identifier": "Test AI Agent"
},
"data": {
"conversation_id": "conv-789",
"conversation_title": "Test Example Conversation",
"bot_id": "bot-001",
"conversation_config_id": "cfg-001",
"channel": "WEBCHAT",
"customer": {
"id": "fb0b4af0-ad6a-48a3-ad3c-d10b237b432c",
"name": "Test User",
"email": "Test@example.com",
"phone": "+123456789",
"address": "Test AI Agent",
"channel": "WEBCHAT"
},
"updated_fields": ["CSAT_SUBMISSION"],
"previous": null,
"current": {"rating": 5, "comment": "Quick and accurate response. Nice experience!"},
"updated_by": {"type": "USER", "id": "user-abc-123", "name": "Test User"},
"updated_at": "2025-06-20T23:44:59.000000"
},
"subscription_created_by": "Test_user",
"subscription_updated_by": "Test_user"
}
message.new
This event is triggered whenever a new message is sent in a conversation. It includes detailed metadata about the message content, sender, and direction.
Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier of the event |
event_type | string | Always "message.new" |
affect | string | Always "add" |
version | string | Event schema version, e.g. "0.0.1" |
timestamp | string | When the event occurred, in ISO 8601 format |
workspace.id | string | Workspace ID associated with the event |
workspace.name | string | Workspace name |
source.id | string | ID of the event source (e.g., AI agent ID) |
source.type | string | Channel type (e.g., WEBCHAT, MESSENGER, etc.) |
source.identifier | string | Identifier or name of the bot or integration |
data.conversation_id | string | ID of the conversation the message belongs to |
data.conversation_title | string | Title of the conversation |
data.bot_id | string (optional) | ID of the bot handling the conversation, or null if none is assigned |
data.conversation_config_id | string (optional) | ID of the conversation’s configuration, or null if none |
data.message_id | string | Unique identifier of the message |
data.direction | string | Message direction (INBOUND or OUTBOUND) |
data.created_at | string | When the message was created (ISO 8601 format) |
data.sender.type | string | Sender type (CUSTOMER, BOT, AGENT, etc.) |
data.sender.id | string | ID of the sender |
data.sender.name | string (optional) | Name of the sender |
data.content.type | string | Type of content (text, image, etc.) |
data.content.text | string | Textual content of the message |
data.content.data | object (optional) | Additional data for rich content (e.g., image URLs, metadata) |
Sample Event Payload
{
"id": "3a3b8bc0-dc3d-43b4-a957-17c2fe1ad153",
"event_type": "message.new",
"affect": "add",
"version": "0.0.1",
"timestamp": "2025-06-20T23:46:00.000000",
"workspace": {
"id": "workspace-123",
"name": "Test Workspace"
},
"source": {
"id": "source-456",
"type": "WEBCHAT",
"identifier": "Test AI Agent"
},
"data": {
"conversation_id": "conv-789",
"conversation_title": "Test Example Conversation",
"bot_id": "bot-001",
"conversation_config_id": "cfg-001",
"message_id": "msg-789",
"direction": "INBOUND",
"created_at": "2025-06-20T23:45:59.000000",
"sender": {
"type": "CUSTOMER",
"id": "fb0b4af0-ad6a-48a3-ad3c-d10b237b432c",
"name": "Test User"
},
"content": {
"type": "text",
"text": "Hello, I need help!",
"data": null
}
},
"subscription_created_by": "Test_user",
"subscription_updated_by": "Test_user"
}
conversation.label.added
This event is triggered when one or more labels are attached to a conversation. It includes details about the updated fields, customer info, who added the label, and when.
Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier of the event |
event_type | string | Always "conversation.label.added" |
affect | string | Always "add" |
version | string | Event schema version, e.g., "0.0.1" |
timestamp | string | When the event occurred, in ISO 8601 format |
workspace.id | string | Workspace ID associated with the event |
workspace.name | string | Workspace name |
source.id | string | ID of the event source |
source.type | string | Channel type (e.g., WEBCHAT, MESSENGER, etc.) |
source.identifier | string | Identifier or name of the bot or integration |
data.conversation_id | string | ID of the conversation |
data.conversation_title | string | Title of the conversation |
data.channel | string | Channel type of the conversation |
data.customer | object | Customer information (id, name, contact info, etc.) |
data.updated_fields | array of string | List of updated fields (likely includes labels) |
data.previous | object (optional) | Previous values of updated fields |
data.current | object (optional) | Current values of updated fields |
data.added_by | object (optional) | The user or bot who added the label |
data.added_at | string | Timestamp when the label was added |
data.labels | array of object | List of label objects (e.g., label id and name) |
Sample Event Payload
{
"id": "ba01d246-3a8c-4e63-a191-28215797388b",
"event_type": "conversation.label.added",
"affect": "add",
"version": "0.0.1",
"timestamp": "2025-06-20T23:50:00.000000",
"workspace": {
"id": "workspace-123",
"name": "Test Workspace"
},
"source": {
"id": "source-456",
"type": "WEBCHAT",
"identifier": "Test AI Agent"
},
"data": {
"conversation_id": "conv-789",
"conversation_title": "Test Example Conversation",
"channel": "WEBCHAT",
"customer": {
"id": "fb0b4af0-ad6a-48a3-ad3c-d10b237b432c",
"name": "Test User",
"email": "Test@example.com",
"phone": "+123456789",
"address": "Test AI Agent",
"channel": "WEBCHAT"
},
"updated_fields": ["labels"],
"previous": {},
"current": {
"labels": [
{
"id": "90c51757408941e2834f76392249b10f",
"color": "#a7927f",
"label": "solved",
"created_at": "2025-05-10T00:20:38.834259",
"updated_at": "2025-05-10T00:20:38.834259",
"description": "",
"workspace_id": Zapier.get_base_sample_data()[0],
},
]
},
"added_by": {
"id": "user-001",
"type": "agent",
"name": "Support Agent"
},
"added_at": "2025-06-20T23:50:00.000000",
"labels": [
{
"id": "90c51757408941e2834f76392249b10f",
"name": "solved",
"type": "CONVERSATION",
"color": "#a7927f",
"is_system": False,
"description": "",
}
]
},
"subscription_created_by": "Test_user",
"subscription_updated_by": "Test_user",
}
conversation.label.deleted
This event is triggered when one or more labels are removed from a conversation. It includes details about which fields changed, who removed the label, and when.
Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier of the event |
event_type | string | Always "conversation.label.deleted" |
affect | string | Always "delete" |
version | string | Event schema version, e.g., "0.0.1" |
timestamp | string | When the event occurred, in ISO 8601 format |
workspace.id | string | Workspace ID associated with the event |
workspace.name | string | Workspace name |
source.id | string | ID of the event source |
source.type | string | Channel type (e.g., WEBCHAT, MESSENGER, etc.) |
source.identifier | string | Identifier or name of the bot or integration |
data.conversation_id | string | ID of the conversation |
data.conversation_title | string | Title of the conversation |
data.channel | string | Channel type of the conversation |
data.customer | object | Customer information (id, name, contact info, etc.) |
data.updated_fields | array of string | List of updated fields (likely includes labels) |
data.previous | object (optional) | Previous values of updated fields |
data.current | object (optional) | Current values of updated fields |
data.removed_by | object (optional) | The user or bot who removed the label |
data.removed_at | string | Timestamp when the label was removed |
data.labels | array of object | List of label objects that were removed |
Sample Event Payload
{
"id": "ab1f7a9b-3a7e-4894-9b35-ef3eaa0e2174",
"event_type": "conversation.label.deleted",
"affect": "delete",
"version": "0.0.1",
"timestamp": "2025-06-21T00:00:00.000000",
"workspace": {
"id": "workspace-123",
"name": "Zapier Workspace"
},
"source": {
"id": "source-456",
"type": "WEBCHAT",
"identifier": "Test AI Agent"
},
"data": {
"conversation_id": "conv-789",
"conversation_title": "Test Example Conversation",
"channel": "WEBCHAT",
"customer": {
"id": "fb0b4af0-ad6a-48a3-ad3c-d10b237b432c",
"name": "Test User",
"email": "Test@example.com",
"phone": "+123456789",
"address": "Test AI Agent",
"channel": "WEBCHAT"
},
"updated_fields": ["labels"],
"previous": {
"labels": [
{
"id": "8d99e9b041f04447858cde7506cee4d5",
"color": "#565e9c",
"label": "newUser",
"created_at": "2025-04-08T02:48:25.059243",
"updated_at": "2025-04-08T02:48:25.059243",
"description": "this is a new user",
"workspace_id": Zapier.get_base_sample_data()[0],
},
{
"id": "90c51757408941e2834f76392249b10f",
"color": "#a7927f",
"label": "solved",
"created_at": "2025-05-10T00:20:38.834259",
"updated_at": "2025-05-10T00:20:38.834259",
"description": "",
"workspace_id": Zapier.get_base_sample_data()[0],
},
]
},
"current": {
[
{
"id": "8d99e9b041f04447858cde7506cee4d5",
"color": "#565e9c",
"label": "newUser",
"created_at": "2025-04-08T02:48:25.059243",
"updated_at": "2025-04-08T02:48:25.059243",
"description": "this is a new user",
"workspace_id": Zapier.get_base_sample_data()[0],
}
]},
"removed_by": {
"id": "user-002",
"type": "agent",
"name": "Support Agent"
},
"removed_at": "2025-06-21T00:00:00.000000",
"labels": [
{
"id": "90c51757408941e2834f76392249b10f",
"name": "solved",
"type": "CONVERSATION",
"color": "#a7927f",
"is_system": False,
"description": "",
}
],
},
"subscription_created_by": "Test_user",
"subscription_updated_by": "Test_user",
}
contact.label.added
This event is triggered when one or more labels are attached to a contact. It includes details about the updated fields, which labels were added, who performed the update, and when.
Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier of the event |
event_type | string | Always "contact.label.added" |
affect | string | Always "add" |
version | string | Event schema version, e.g., "0.0.1" |
timestamp | string | When the event occurred, in ISO 8601 format |
workspace.id | string | Workspace ID associated with the event |
workspace.name | string | Workspace name |
data.contact_id | string | ID of the contact |
data.contact_name | string | Name of the contact |
data.updated_fields | array of string | List of updated fields (includes labels) |
data.previous.labels | array of object | Labels before the update |
data.current.labels | array of object | Labels after the update |
data.labels | array of object | The label objects that were added in this change |
data.updated_by | object | The user or system who made the update |
data.updated_at | string | Timestamp when the update happened |
Sample Event Payload
{
"id": "9a6b0a1e-0d8e-4b3e-8c8a-6cd77c7e13b1",
"event_type": "contact.label.added",
"affect": "add",
"version": "0.0.1",
"timestamp": "2025-06-20T23:44:30.000000",
"workspace": { "id": "workspace-123", "name": "Zapier Workspace" },
"data": {
"contact_id": "c0b8f2e2-2d6d-49b5-8e8e-4b2b0c9b7f1a",
"contact_name": "Zapier Contact",
"updated_fields": ["labels"],
"previous": {
"labels": [
{
"id": "8d99e9b041f04447858cde7506cee4d5",
"name": "newUser",
"type": "CONTACT",
"color": "#565e9c",
"is_system": false,
"description": "this is a new user"
}
]
},
"current": {
"labels": [
{
"id": "8d99e9b041f04447858cde7506cee4d5",
"name": "newUser",
"type": "CONTACT",
"color": "#565e9c",
"is_system": false,
"description": "this is a new user"
},
{
"id": "90c51757408941e2834f76392249b10f",
"name": "VIP",
"type": "CONTACT",
"color": "#a7927f",
"is_system": false,
"description": ""
}
]
},
"labels": [
{
"id": "90c51757408941e2834f76392249b10f",
"name": "VIP",
"type": "CONTACT",
"color": "#a7927f",
"is_system": false,
"description": ""
}
],
"updated_by": { "type": "USER", "id": "user-12345", "name": "Test User" },
"updated_at": "2025-06-20T23:44:30.000000"
},
"subscription_created_by": "Test_user",
"subscription_updated_by": "Test_user"
}
contact.label.deleted
This event is triggered when one or more labels are removed from a contact. It includes details about which labels were removed, who performed the update, and when.
Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier of the event |
event_type | string | Always "contact.label.deleted" |
affect | string | Always "delete" |
version | string | Event schema version, e.g., "0.0.1" |
timestamp | string | When the event occurred, in ISO 8601 format |
workspace.id | string | Workspace ID associated with the event |
workspace.name | string | Workspace name |
data.contact_id | string | ID of the contact |
data.contact_name | string | Name of the contact |
data.updated_fields | array of string | List of updated fields (includes labels) |
data.previous.labels | array of object | Labels before the change |
data.current.labels | array of object | Labels after the change |
data.labels | array of object | The label objects that were removed in this change |
data.updated_by | object | The user or system who made the update |
data.updated_at | string | Timestamp when the update happened |
Sample Event Payload
{
"id": "b1c6de8f-4d0b-44f0-97f1-1a2b3c4d5e6f",
"event_type": "contact.label.deleted",
"affect": "delete",
"version": "0.0.1",
"timestamp": "2025-06-20T23:44:30.000000",
"workspace": { "id": "workspace-123", "name": "Zapier Workspace" },
"data": {
"contact_id": "c0b8f2e2-2d6d-49b5-8e8e-4b2b0c9b7f1a",
"contact_name": "Zapier Contact",
"updated_fields": ["labels"],
"previous": {
"labels": [
{
"id": "8d99e9b041f04447858cde7506cee4d5",
"name": "newUser",
"type": "CONTACT",
"color": "#565e9c",
"is_system": false,
"description": "this is a new user"
},
{
"id": "90c51757408941e2834f76392249b10f",
"name": "VIP",
"type": "CONTACT",
"color": "#a7927f",
"is_system": false,
"description": ""
}
]
},
"current": {
"labels": [
{
"id": "8d99e9b041f04447858cde7506cee4d5",
"name": "newUser",
"type": "CONTACT",
"color": "#565e9c",
"is_system": false,
"description": "this is a new user"
}
]
},
"labels": [
{
"id": "90c51757408941e2834f76392249b10f",
"name": "VIP",
"type": "CONTACT",
"color": "#a7927f",
"is_system": false,
"description": ""
}
],
"updated_by": { "type": "USER", "id": "user-12345", "name": "Test User" },
"updated_at": "2025-06-20T23:44:30.000000"
},
"subscription_created_by": "Test_user",
"subscription_updated_by": "Test_user"
}
Get a list of contact labels of your workspace
Use this endpoint to list all contact labels in your workspace and resolve label
IDs referenced in contact.label.added and contact.label.deleted webhook
events.
GET https://seax.seasalt.ai/seax-api/api/v1/workspace/{workspace_id}/contact_labels
Parameters:
| Field | Type | Description | Allowed Values / Example | Required |
|---|---|---|---|---|
X-API-Key | string (header) | API key for authorization (see Authorization) | <your_api_key> | |
workspace_id | string (path) | Workspace ID | 3fa85f64-5717-4562-b3fc-2c963f66afa6 | ✅ |
keyword | string (query) | Optional, determine the keyword to search contact names and phones. | +18111222333 | |
offset | integer (query) | Rows to skip | 0 | |
limit | integer (query) | Max rows to return (0 = all) | 10 | |
order_by | string (query) | Sort by comma-separated <field>:<direction> pairs | name:desc (default) | |
is_system | boolean (query) | Filter by system labels | true/false | |
exclude_labels | string (query) | Exclude labels (comma-separated names) | invalid number,unreachable | |
exclude_labels_for_count | string (query) | Exclude labels when calculating contact counts (comma-separated names) | DNC,invalid number,unreachable | |
contact_counting | boolean (query) | Return contact count per label (default: true) | true/false | |
whatsapp_phone_only | boolean (query) | Only include contacts that have WhatsApp phone when counting | true/false | |
phone_only | boolean (query) | Only include contacts that have a phone when counting | true/false |
Request:
curl -X 'GET' \
'https://seax.seasalt.ai/seax-api/api/v1/workspace/3fa85f64-5717-4562-b3fc-2c963f66afa6/contact_labels?keyword=VIP&offset=0&limit=10&order_by=name:desc&contact_counting=true&phone_only=false&whatsapp_phone_only=false' \
-H 'accept: application/json' \
-H 'X-API-Key: <your_api_key>'
Response:
{
"data": [
{
"id": "8d99e9b041f04447858cde7506cee4d5",
"name": "newUser",
"type": "CONTACT",
"color": "#565e9c",
"is_system": false,
"description": "this is a new user"
},
{
"id": "90c51757408941e2834f76392249b10f",
"name": "VIP",
"type": "CONTACT",
"color": "#a7927f",
"is_system": false,
"description": ""
}
],
"total": 2,
"offset": 0,
"limit": 10
}
Get a list of contacts of your workspace
Use this endpoint to list contacts and obtain contact_id values referenced by
contact.label.* webhook payloads.
GET https://seax.seasalt.ai/seax-api/api/v1/workspace/{workspace_id}/contacts
Parameters:
| Field | Type | Description | Allowed Values / Example | Required |
|---|---|---|---|---|
X-API-Key | string (header) | Authorization with APIKey | <your_api_key> | |
workspace_id | string (path) | Workspace ID | 3fa85f64-5717-4562-b3fc-2c963f66afa6 | ✅ |
offset | integer (query) | Optional, number of rows to skip | 0 | |
limit | integer (query) | Optional, number of rows to return after offset (0 = all) | 10 | |
keyword | string (query) | Optional, keyword to search contact names and phones | +18111222333 | |
whatsapp_phone | string (query) | Optional, search contacts by exact WhatsApp phone | +18111222333 | |
all_contact_label_ids | string (query) | Optional, contacts must match all label IDs (comma-separated UUIDs) | 11111111-2222-4444-3333-555555555555,11111111-2222-4444-3333-666666666666 | |
any_contact_label_ids | string (query) | Optional, contacts match any label IDs (comma-separated UUIDs) | 11111111-2222-4444-3333-555555555555,11111111-2222-4444-3333-666666666666 | |
exclude_contact_ids | string (query) | Optional, exclude contacts by IDs (comma-separated UUIDs) | 11111111-2222-4444-3333-555555555555,11111111-2222-4444-3333-666666666666 | |
exclude_any_contact_label_ids | string (query) | Optional, exclude contacts that match any of these label IDs (comma-separated UUIDs) | 11111111-2222-4444-3333-555555555555,11111111-2222-4444-3333-666666666666 | |
exclude_all_contact_label_ids | string (query) | Optional, exclude contacts that match all of these label IDs (comma-separated UUIDs) | 11111111-2222-4444-3333-555555555555,11111111-2222-4444-3333-666666666666 | |
addition_contact_ids | string (query) | Optional, force-include these contacts in result (comma-separated UUIDs) | 11111111-2222-4444-3333-555555555555,11111111-2222-4444-3333-666666666666 | |
order_by | string (query) | Optional, comma-separated list of <field>:<direction> pairs (default: created_time:desc) | phone:asc,created_time:desc,name:asc | |
exclude_labels | string (query) | Optional, exclude by label names (comma-separated). Affects label-based filtering and counts | DNC,invalid number,unreachable | |
whatsapp_phone_only | boolean (query) | Optional, only include contacts that match whatsapp_phone | false | |
phone_only | boolean (query) | Optional, only include contacts that have a phone number | false |
Request:
curl -X 'GET' \
'https://seax.seasalt.ai/seax-api/api/v1/workspace/3fa85f64-5717-4562-b3fc-2c963f66afa6/contacts?offset=0&limit=10&keyword=%2B18111222333&any_contact_label_ids=11111111-2222-4444-3333-555555555555&exclude_labels=DNC,invalid%20number' \
-H 'accept: application/json' \
-H 'X-API-Key: <your_api_key>'
Response (truncated):
{
"data": [
{
"id": "11111111-2222-4444-3333-555555555555",
"name": "John Doe",
"phone": "+12345678900",
"whatsapp_phone": "+12345678900",
"contact_labels": [
{
"id": "11111111-2222-4444-3333-555555555555",
"name": "vip_customers",
"is_system": false
}
]
}
],
"total": 1
}
call.new
This event is triggered when a new call is initiated. It includes information about the caller, callee, call direction, channel, and metadata.
Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier of the event |
event_type | string | Always "call.new" |
affect | string | Always "add" |
version | string | Event schema version, e.g., "0.0.1" |
timestamp | string | When the event occurred, in ISO 8601 format |
workspace.id | string | Workspace ID associated with the event |
workspace.name | string | Workspace name |
source.id | string | ID of the event source |
source.type | string | Channel type (e.g., WEBCHAT, MESSENGER, etc.) |
source.identifier | string | Identifier or name of the bot or integration |
data.conversation_id | string | ID of the associated conversation |
data.conversation_title | string | Title of the conversation |
data.bot_id | string (optional) | ID of the bot handling the call, or null if none is assigned |
data.conversation_config_id | string (optional) | ID of the conversation’s configuration, or null if none |
data.channel | string | Channel where the call took place |
data.direction | string | INBOUND or OUTBOUND |
data.call_from | object | Entity initiating the call (type, id, name, address) |
data.call_to | object | Entity receiving the call (type, id, name, address) |
data.started_at | string | Timestamp when the call started (ISO 8601 format) |
data.metadata | object (optional) | Additional metadata, such as SIP info or system details |
Sample Event Payload
{
"id": "e1b57c9f-dcd9-4bc1-bdc8-cfc9960c011e",
"event_type": "call.new",
"affect": "add",
"version": "0.0.1",
"timestamp": "2025-06-21T09:00:00.000000",
"workspace": {
"id": "workspace-456",
"name": "Test Workspace"
},
"source": {
"id": "source-999",
"type": "SEAX_CALL",
"identifier": "Test Callbot"
},
"data": {
"conversation_id": "conv-call-001",
"conversation_title": "+11234567890",
"bot_id": "bot-001",
"conversation_config_id": "cfg-001",
"channel": "SEAX_CALL",
"direction": "INBOUND",
"started_at": "2025-06-21T09:00:00.000000",
"call_from": {
"id": "+123456789",
"name": "Zapier AI Agent",
"type": "AGENT",
"address": "+123456789",
"conversation_id": "123",
"conversation_title": "+123456789"
},
"call_to": {
"id": "agent-001",
"name": "+123456789",
"type": "CUSTOMER",
"address": "+123456789",
"conversation_id": "123",
"conversation_title": "+123456789"
},
"metadata": {
"sip_session_id": "abc123",
"recording_enabled": true
}
},
"subscription_created_by": "Test_user",
"subscription_updated_by": "Test_user"
}
call.ended
This event is triggered when a call ends. It contains the call participants, duration, reason for ending, and optional recording information.
Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier of the event |
event_type | string | Always "call.ended" |
affect | string | Always "delete" |
version | string | Event schema version, e.g., "0.0.1" |
timestamp | string | When the event occurred, in ISO 8601 format |
workspace.id | string | Workspace ID associated with the event |
workspace.name | string | Workspace name |
source.id | string | ID of the event source |
source.type | string | Channel type (e.g., SEAX_CALL, WHATSAPP) |
source.identifier | string | Identifier or name of the bot or integration |
data.conversation_id | string | ID of the associated conversation |
data.conversation_title | string | Title of the conversation |
data.bot_id | string (optional) | ID of the bot handling the call, or null if none is assigned |
data.conversation_config_id | string (optional) | ID of the conversation’s configuration, or null if none |
data.channel | string | Channel where the call took place |
data.direction | string | INBOUND or OUTBOUND |
data.duration_seconds | integer | Duration of the call in seconds |
data.finish_reason | string | Reason the call ended (e.g., completed, abandoned, failed) |
data.call_from | object | Caller info (type, id, name, address) |
data.call_to | object | Callee info (type, id, name, address) |
data.finished_at | string | When the call ended (ISO 8601) |
data.finished_by | string | Who ended the call (timestamp) |
data.recording_available | boolean | Whether a recording is available |
data.recording_url | string (optional) | URL to download the call recording |
Sample Event Payload
{
"id": "2c72e50e-68ae-4a97-8f20-cdffedexample",
"event_type": "call.ended",
"affect": "delete",
"version": "0.0.1",
"timestamp": "2025-06-21T09:15:00.000000",
"workspace": {
"id": "workspace-456",
"name": "Test Workspace"
},
"source": {
"id": "source-999",
"type": "SEAX_CALL",
"identifier": "Test Callbot"
},
"data": {
"conversation_id": "conv-call-001",
"conversation_title": "+11234567890",
"bot_id": "bot-001",
"conversation_config_id": "cfg-001",
"channel": "SEAX_CALL",
"direction": "INBOUND",
"duration_seconds": 300,
"finish_reason": "completed",
"call_from": {
"id": "+123456789",
"name": "Zapier AI Agent",
"type": "AGENT",
"address": "+123456789",
"conversation_id": "123",
"conversation_title": "+123456789"
},
"call_to": {
"id": "agent-001",
"name": "+123456789",
"type": "CUSTOMER",
"address": "+123456789",
"conversation_id": "123",
"conversation_title": "+123456789"
},
"finished_at": "2025-06-21T09:15:00.000000",
"finished_by": "2025-06-21T09:15:00.000000",
"recording_available": true,
"recording_url": "https://recordings.example.com/recording1234.mp3"
},
"subscription_created_by": "Test_user",
"subscription_updated_by": "Test_user"
}
call.updated
This event is triggered when details of an ongoing or completed call are updated. It is typically used to reflect changes such as transcription availability, additional metadata, or corrections.
Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier of the event |
event_type | string | Always "call.updated" |
affect | string | Always "update" |
version | string | Event schema version, e.g., "0.0.1" |
timestamp | string | When the event occurred, in ISO 8601 format |
workspace.id | string | Workspace ID associated with the event |
workspace.name | string | Workspace name |
source.id | string | ID of the event source |
source.type | string | Channel type (e.g., SEAX_CALL, WHATSAPP) |
source.identifier | string | Identifier or name of the bot or integration |
data.conversation_id | string | ID of the associated conversation |
data.conversation_title | string | Title of the conversation |
data.bot_id | string (optional) | ID of the bot handling the call, or null if none is assigned |
data.conversation_config_id | string (optional) | ID of the conversation’s configuration, or null if none |
data.call_id | string (optional) | ID of the call being updated |
data.update_reason | string (optional) | Description of why the call was updated |
data.previous | object (optional) | Previous values of updated fields |
data.current | object (optional) | New/current values of updated fields |
data.updated_at | string | When the update occurred (ISO 8601 format) |
data.resources | object (optional) | Additional attached data, such as transcription or analysis |
Sample Event Payload
{
"id": "fcfe4bfa-8e04-4710-a10a-9f20c5e83ee3",
"event_type": "call.updated",
"affect": "update",
"version": "0.0.1",
"timestamp": "2025-06-22T10:10:00.000000",
"workspace": {
"id": "workspace-456",
"name": "Test Workspace"
},
"source": {
"id": "source-999",
"type": "SEAX_CALL",
"identifier": "Test Callbot"
},
"data": {
"conversation_id": "conv-call-001",
"conversation_title": "+11234567890",
"bot_id": "bot-001",
"conversation_config_id": "cfg-001",
"call_id": "call-abc-123",
"update_reason": "recording_ready",
"previous": {
"recording_url": null
},
"current": {
"recording_url": "https://recordings.example.com/recording1234.mp3"
},
"updated_at": "2025-06-22T10:10:00.000000",
"resources": {
"session_id": "789456123",
"channel_type": "SEAX_CALL",
"conversation_id": "123456"
}
},
"subscription_created_by": "Test_user",
"subscription_updated_by": "Test_user"
}
meeting.ended
This event is triggered when a meeting From SeaMeet ends. It contains comprehensive information about the meeting including transcriptions, summary, action items, participants, and analysis results. You can subscribe to this event if any of these apply:
- You are a user of SeaMeet (https://seameet.ai), you want to register a webhook when your Google Meet, Microsoft Teams or Zoom meeting ends.
- You have an integration with SeaMeet through SeaX (https://seasalt.ai/en/seax), you use SeaMeet to analyze all your phone calls and want to register a webhook when a call ends.
Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier of the event |
event_type | string | Always "meeting.ended" |
affect | string | Always "add" |
version | string | Event schema version, e.g., "0.0.1" |
timestamp | string | When the event occurred, in ISO 8601 format |
workspace.id | string | Workspace ID associated with the event |
workspace.name | string | Workspace name |
source.id | string | ID of the event source |
source.type | string | Channel type (always "SEAMEET") |
source.identifier | string | Identifier or name of the SeaMeet integration |
data.meeting_id | string | Unique identifier of the meeting |
data.meeting_start_time | string | When the meeting started (ISO 8601 format) |
data.meeting_end_time | string | When the meeting ended (ISO 8601 format) |
data.duration_seconds | integer | Duration of the meeting in seconds |
data.transcriptions | array of object | Array of transcription segments |
data.transcriptions[].id | string | Unique identifier of the transcription segment |
data.transcriptions[].transcription | string | Transcribed text content |
data.transcriptions[].speaker | string | Name of the speaker |
data.transcriptions[].anchor_start | number | Start time of the segment in seconds |
data.transcriptions[].duration | number | Duration of the segment in seconds |
data.summary | string | AI-generated summary of the meeting |
data.action_items | array of string | List of action items identified from the meeting |
data.participants | array of string | List of meeting participants |
data.labels | array of string | Labels associated with the meeting |
data.custom_analysis_results | object | Custom analysis results |
data.custom_analysis_results.id | string | Meeting UUID for analysis |
data.custom_analysis_results.datetime_utc | string | Meeting datetime in UTC |
data.custom_analysis_results.datetime_local | string | Meeting datetime in local time |
data.custom_analysis_results.timezone | string | Timezone of the meeting |
data.custom_analysis_results.call_direction | string | Direction of the call (Inbound/Outbound) |
data.custom_analysis_results.call_duration | integer | Call duration in seconds |
data.custom_analysis_results.agent_name | string | Name of the agent |
data.custom_analysis_results.agent_phone | string | Phone number of the agent |
data.custom_analysis_results.customer_name | string | Name of the customer |
data.custom_analysis_results.customer_phone | string | Phone number of the customer |
data.custom_analysis_results.topics | string | Topics discussed in the meeting |
data.custom_analysis_results.labels | string | Analysis labels |
data.custom_analysis_results.summary | string | Summary from analysis |
data.custom_analysis_results.analyses | array of object | Array of analysis results |
data.custom_analysis_results.analyses[].analysis_key | string | Key for the analysis type |
data.custom_analysis_results.analyses[].analysis_subkey | string | Subkey for specific analysis aspect |
data.custom_analysis_results.analyses[].analysis_value | string | Value/result of the analysis |
Sample Event Payload
{
"id": "f4e6d8b2-9c1a-4f3e-8d7b-2a5c8f9e1d3a",
"event_type": "meeting.ended",
"affect": "add",
"version": "0.0.1",
"timestamp": "2024-01-15T15:15:00.000000",
"workspace": {
"id": "workspace-789",
"name": "Zapier Workspace"
},
"source": {
"id": "seameet-source-123",
"type": "SEAMEET",
"identifier": "SeaMeet AI Agent"
},
"data": {
"meeting_id": "meet_456def",
"meeting_start_time": "2024-01-15T14:30:00Z",
"meeting_end_time": "2024-01-15T15:15:00Z",
"duration_seconds": 2700,
"transcriptions": [
{
"id": "trans_001",
"transcription": "Let's start with the quarterly review.",
"speaker": "John Doe",
"anchor_start": 15.0,
"duration": 3.5
},
{
"id": "trans_002",
"transcription": "I have the numbers ready for presentation.",
"speaker": "Jane Smith",
"anchor_start": 80.0,
"duration": 4.2
}
],
"summary": "The team discussed project progress and assigned action items for the next sprint.",
"action_items": [
"Review the quarterly report",
"Prepare slides for the client presentation"
],
"participants": ["John Doe", "Jane Smith", "Bob Wilson"],
"labels": ["followup required", "missed"],
"custom_analysis_results": {
"id": "meeting-uuid",
"datetime_utc": "2024-07-26T04:08:00",
"datetime_local": "2024-07-26T04:08:00",
"timezone": "US/Pacific",
"call_direction": "Outbound",
"call_duration": 61,
"agent_name": "Sabrina",
"agent_phone": "+1234567890",
"customer_name": "Kim",
"customer_phone": "+0987654321",
"topics": "Appointment,Scheduling",
"labels": "Existing Customer",
"summary": "Kim called in to make an appointment for her dog.",
"analyses": [
{
"analysis_key": "triage",
"analysis_subkey": "urgency_flag",
"analysis_value": "GREEN"
},
{
"analysis_key": "sentiment",
"analysis_subkey": "overall",
"analysis_value": "positive"
}
]
}
},
"subscription_created_by": "Test_user",
"subscription_updated_by": "Test_user"
}
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.