Seasalt.ai Webhook Notification API Tutorial

Learn how to use Seasalt.ai’s Webhook Notification API to receive real-time event updates for conversations, calls, and user actions. Ideal for integrations with Zapier and custom automation workflows.

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 Notification as the scope.

  • Copy the key and keep it safe. This key is required in the X-API-KEY header 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-Signature header 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

FieldTypeRequiredDescription
webhook_urlstring✅The publicly accessible URL to receive webhook events.
event_typesarray of string✅List of event types to subscribe to. See supported event types below.
created_bystringIdentifier of the user creating the subscription.
is_enabledboolean✅Whether the subscription is active (true) or paused (false).
typestringSubscription 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.new
  • conversation.updated
  • message.new
  • conversation.label.added
  • conversation.label.deleted
  • contact.label.added
  • contact.label.deleted
  • conversation.ended
  • call.new
  • call.updated
  • call.ended
  • meeting.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.

NameTypeRequiredDefaultDescription
order_bystringcreated_at:descOrder items by field and direction. Format: field:direction
typestringFilter 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 ValueDescription
created_at:ascOldest subscriptions first
created_at:descNewest subscriptions first (default)
updated_at:ascLeast recently updated subscriptions first
updated_at:descMost recently updated subscriptions first
id:ascSort by subscription ID (A–Z)
id:descSort by subscription ID (Z–A)
created_by:ascCreator A–Z
created_by:descCreator Z–A
updated_by:ascLast updater A–Z
updated_by:descLast updater Z–A
is_enabled:ascInactive subscriptions first
is_enabled:descActive subscriptions first
type:ascSubscription type A–Z (SEASALT, ZAPIER)
type:descSubscription 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

FieldTypeRequiredDescription
webhook_urlstringNoThe URL to which webhook events will be delivered.
event_typesstring[]NoA list of event types you want to subscribe to.
is_enabledbooleanNoWhether the subscription is active.
typestringNoType of subscription. Enum: SEASALT, ZAPIER.
updated_bystringYesEmail 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

FieldTypeRequiredDescription
overlap_hoursintegerHow 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

NameTypeRequiredDescription
event_typestringYesThe type of event to simulate (e.g. conversation.new)
webhook_urlstringYesThe URL to which the test payload will be sent
subscription_idstringNoIf 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"]
  }
}
FieldTypeDescription
signed_bodystringThe 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.
timestampintegerThe Unix epoch timestamp used in the signature (the t= field of Seasalt-Signature). Compare against your own clock to rule out skew.
expected_signaturestringThe exact Seasalt-Signature header value this test delivery computed and sent.
secrets_usedarray of stringThe 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:

SymptomLikely cause
Every delivery failsVerifying the parsed/re-serialized body instead of the raw bytes
Intermittent failuresReceiver clock skew beyond the 300-second tolerance
Started failing suddenly, was fine beforeRotation overlap expired — deploy the new secret
Fails only for some event typesBody 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.

NameTypeDescription
event_typestringFilter logs by event type (e.g. conversation.new)
delivery_statusstringFilter by delivery status (success, failed)
start_datedatetimeReturn 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_datedatetimeReturn 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_bystringSort results (default: created_at:desc). Format: field:direction
limitintegerMaximum number of results (default: 0 for unlimited)
offsetintegerNumber 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 ValueDescription
created_at:ascOldest logs first
created_at:descNewest logs first (default)
event_type:ascEvent type A–Z
event_type:descEvent type Z–A
delivery_status:ascStatus A–Z (e.g. failed before success)
delivery_status:descStatus Z–A
status_code:ascLower HTTP status codes first (e.g. 200 → 500)
status_code:descHigher 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.

NameTypeDescription
event_typestringFilter logs by event type (e.g. conversation.new)
delivery_statusstringFilter by delivery status (success, failed)
start_datedatetimeReturn 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_datedatetimeReturn 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_bystringSort results (default: created_at:desc). Format: field:direction
limitintegerMaximum number of results (default: 0 for unlimited)
offsetintegerNumber 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 ValueDescription
created_at:ascOldest logs first
created_at:descNewest logs first (default)
event_type:ascEvent type A–Z
event_type:descEvent type Z–A
delivery_status:ascStatus A–Z (e.g. failed before success)
delivery_status:descStatus Z–A
status_code:ascLower HTTP status codes first (e.g. 200 → 500)
status_code:descHigher 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

NameTypeRequiredDescription
emailstring✅The email address to receive the download link.
start_datedatetimeStart 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_datedatetimeEnd 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)
langstringEither 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:

HeaderExampleMeaning
Seasalt-Signaturet=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-Id9b1d2e3f-...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-Typemessage.newConvenience copy of the event type already present in the body. Not covered by the signature.
Seasalt-Subscription-Idsub_...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:

  1. Log only. Compute the signature, log whether it matched, and still return 200 regardless of the result. Run until your logs are clean.
  2. 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.

SymptomLikely cause
Every delivery failsVerifying the parsed/re-serialized body instead of the raw bytes
Intermittent failuresReceiver clock skew beyond the 300-second tolerance
Started failing suddenly, was fine beforeRotation overlap expired — deploy the new secret
Fails only for some event typesBody 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

FieldTypeDescription
idstringUnique identifier of the event
event_typestringAlways "conversation.new"
affectstringType of change, usually "add"
versionstringEvent schema version, e.g. "0.0.1"
timestampstringWhen the event occurred, in ISO 8601 format
workspace.idstringWorkspace ID associated with the event
workspace.namestringWorkspace name
source.idstringID of the event source (e.g., AI agent ID)
source.typestringChannel type (e.g., WEBCHAT, MESSENGER, etc.)
source.identifierstringIdentifier or name of the bot or integration
data.conversation_idstringID of the new conversation
data.conversation_titlestringTitle of the conversation
data.bot_idstring (optional)ID of the bot handling the conversation, or null if none is assigned
data.conversation_config_idstring (optional)ID of the conversation’s configuration, or null if none
data.channelstringChannel where the conversation started
data.customer.idstringCustomer ID
data.customer.namestringCustomer name
data.customer.emailstringCustomer email address
data.customer.phonestringCustomer phone number
data.customer.addressstringAddress or source identifier
data.customer.channelstringCustomer’s channel (should match data.channel)
data.latest_inbound_message.idstringID of the latest inbound message
data.latest_inbound_message.directionstringAlways "INBOUND"
data.latest_inbound_message.textstringMessage content
data.latest_inbound_message.typestringType of message (e.g. "text")
data.latest_inbound_message.created_atstringTimestamp of message creation (ISO 8601)
data.latest_outbound_message.idstringID of the latest outbound message
data.latest_outbound_message.directionstringAlways "OUTBOUND"
data.latest_outbound_message.textstringMessage content
data.latest_outbound_message.typestringType of message (e.g. "text")
data.latest_outbound_message.created_atstringTimestamp 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

FieldTypeDescription
idstringUnique identifier of the event
event_typestringAlways "conversation.updated"
affectstringType of change, typically "change"
versionstringEvent schema version, e.g. "0.0.1"
timestampstringWhen the event occurred, in ISO 8601 format
workspace.idstringWorkspace ID associated with the event
workspace.namestringWorkspace name
source.idstringID of the event source (e.g., AI agent ID)
source.typestringChannel type (e.g., WEBCHAT, MESSENGER, etc.)
source.identifierstringIdentifier or name of the bot or integration
data.conversation_idstringID of the updated conversation
data.conversation_titlestringTitle of the conversation
data.bot_idstring (optional)ID of the bot handling the conversation, or null if none is assigned
data.conversation_config_idstring (optional)ID of the conversation’s configuration, or null if none
data.channelstringChannel where the conversation takes place
data.customer.idstringCustomer ID
data.customer.namestringCustomer name
data.customer.emailstringCustomer email address
data.customer.phonestringCustomer phone number
data.customer.addressstringAddress or source identifier
data.customer.channelstringCustomer’s channel (should match data.channel)
data.updated_fieldsarray of stringList of fields that were updated
data.previousobjectPrevious values before the update
data.currentobjectCurrent values after the update
data.updated_by.idstring (optional)ID of the user or agent who made the update
data.updated_by.namestring (optional)Name of the user or agent who made the update
data.updated_by.typestring (optional)Type of entity (e.g., agent, system)
data.updated_atstringTimestamp 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

FieldTypeDescription
idstringUnique identifier of the event
event_typestringAlways "conversation.ended"
affectstringAlways "delete"
versionstringEvent schema version, e.g. "0.0.1"
timestampstringWhen the event occurred, in ISO 8601 format
workspace.idstringWorkspace ID associated with the event
workspace.namestringWorkspace name
source.idstringID of the event source (e.g., AI agent ID)
source.typestringChannel type (e.g., WEBCHAT, MESSENGER, etc.)
source.identifierstringIdentifier or name of the bot or integration
data.conversation_idstringID of the ended conversation
data.conversation_titlestringTitle of the conversation
data.bot_idstring (optional)ID of the bot handling the conversation, or null if none is assigned
data.conversation_config_idstring (optional)ID of the conversation’s configuration, or null if none
data.channelstringChannel where the conversation took place
data.customer.idstringCustomer ID
data.customer.namestringCustomer name
data.customer.emailstringCustomer email address
data.customer.phonestringCustomer phone number
data.customer.addressstringAddress or source identifier
data.customer.channelstringCustomer’s channel (should match data.channel)
data.updated_fieldsarray of stringList of fields that were changed before the conversation ended
data.previousobjectPrevious values before the final update
data.currentobjectFinal values at the moment the conversation ended
data.updated_by.idstring (optional)ID of the user or agent who ended the conversation
data.updated_by.namestring (optional)Name of the user or agent
data.updated_by.typestring (optional)Type of entity (e.g., agent, system)
data.updated_atstringTimestamp 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

FieldTypeDescription
idstringUnique identifier of the event
event_typestringAlways "message.new"
affectstringAlways "add"
versionstringEvent schema version, e.g. "0.0.1"
timestampstringWhen the event occurred, in ISO 8601 format
workspace.idstringWorkspace ID associated with the event
workspace.namestringWorkspace name
source.idstringID of the event source (e.g., AI agent ID)
source.typestringChannel type (e.g., WEBCHAT, MESSENGER, etc.)
source.identifierstringIdentifier or name of the bot or integration
data.conversation_idstringID of the conversation the message belongs to
data.conversation_titlestringTitle of the conversation
data.bot_idstring (optional)ID of the bot handling the conversation, or null if none is assigned
data.conversation_config_idstring (optional)ID of the conversation’s configuration, or null if none
data.message_idstringUnique identifier of the message
data.directionstringMessage direction (INBOUND or OUTBOUND)
data.created_atstringWhen the message was created (ISO 8601 format)
data.sender.typestringSender type (CUSTOMER, BOT, AGENT, etc.)
data.sender.idstringID of the sender
data.sender.namestring (optional)Name of the sender
data.content.typestringType of content (text, image, etc.)
data.content.textstringTextual content of the message
data.content.dataobject (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

FieldTypeDescription
idstringUnique identifier of the event
event_typestringAlways "conversation.label.added"
affectstringAlways "add"
versionstringEvent schema version, e.g., "0.0.1"
timestampstringWhen the event occurred, in ISO 8601 format
workspace.idstringWorkspace ID associated with the event
workspace.namestringWorkspace name
source.idstringID of the event source
source.typestringChannel type (e.g., WEBCHAT, MESSENGER, etc.)
source.identifierstringIdentifier or name of the bot or integration
data.conversation_idstringID of the conversation
data.conversation_titlestringTitle of the conversation
data.channelstringChannel type of the conversation
data.customerobjectCustomer information (id, name, contact info, etc.)
data.updated_fieldsarray of stringList of updated fields (likely includes labels)
data.previousobject (optional)Previous values of updated fields
data.currentobject (optional)Current values of updated fields
data.added_byobject (optional)The user or bot who added the label
data.added_atstringTimestamp when the label was added
data.labelsarray of objectList 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

FieldTypeDescription
idstringUnique identifier of the event
event_typestringAlways "conversation.label.deleted"
affectstringAlways "delete"
versionstringEvent schema version, e.g., "0.0.1"
timestampstringWhen the event occurred, in ISO 8601 format
workspace.idstringWorkspace ID associated with the event
workspace.namestringWorkspace name
source.idstringID of the event source
source.typestringChannel type (e.g., WEBCHAT, MESSENGER, etc.)
source.identifierstringIdentifier or name of the bot or integration
data.conversation_idstringID of the conversation
data.conversation_titlestringTitle of the conversation
data.channelstringChannel type of the conversation
data.customerobjectCustomer information (id, name, contact info, etc.)
data.updated_fieldsarray of stringList of updated fields (likely includes labels)
data.previousobject (optional)Previous values of updated fields
data.currentobject (optional)Current values of updated fields
data.removed_byobject (optional)The user or bot who removed the label
data.removed_atstringTimestamp when the label was removed
data.labelsarray of objectList 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

FieldTypeDescription
idstringUnique identifier of the event
event_typestringAlways "contact.label.added"
affectstringAlways "add"
versionstringEvent schema version, e.g., "0.0.1"
timestampstringWhen the event occurred, in ISO 8601 format
workspace.idstringWorkspace ID associated with the event
workspace.namestringWorkspace name
data.contact_idstringID of the contact
data.contact_namestringName of the contact
data.updated_fieldsarray of stringList of updated fields (includes labels)
data.previous.labelsarray of objectLabels before the update
data.current.labelsarray of objectLabels after the update
data.labelsarray of objectThe label objects that were added in this change
data.updated_byobjectThe user or system who made the update
data.updated_atstringTimestamp 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

FieldTypeDescription
idstringUnique identifier of the event
event_typestringAlways "contact.label.deleted"
affectstringAlways "delete"
versionstringEvent schema version, e.g., "0.0.1"
timestampstringWhen the event occurred, in ISO 8601 format
workspace.idstringWorkspace ID associated with the event
workspace.namestringWorkspace name
data.contact_idstringID of the contact
data.contact_namestringName of the contact
data.updated_fieldsarray of stringList of updated fields (includes labels)
data.previous.labelsarray of objectLabels before the change
data.current.labelsarray of objectLabels after the change
data.labelsarray of objectThe label objects that were removed in this change
data.updated_byobjectThe user or system who made the update
data.updated_atstringTimestamp 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:

FieldTypeDescriptionAllowed Values / ExampleRequired
X-API-Keystring (header)API key for authorization (see Authorization)<your_api_key>
workspace_idstring (path)Workspace ID3fa85f64-5717-4562-b3fc-2c963f66afa6✅
keywordstring (query)Optional, determine the keyword to search contact names and phones.+18111222333
offsetinteger (query)Rows to skip0
limitinteger (query)Max rows to return (0 = all)10
order_bystring (query)Sort by comma-separated <field>:<direction> pairsname:desc (default)
is_systemboolean (query)Filter by system labelstrue/false
exclude_labelsstring (query)Exclude labels (comma-separated names)invalid number,unreachable
exclude_labels_for_countstring (query)Exclude labels when calculating contact counts (comma-separated names)DNC,invalid number,unreachable
contact_countingboolean (query)Return contact count per label (default: true)true/false
whatsapp_phone_onlyboolean (query)Only include contacts that have WhatsApp phone when countingtrue/false
phone_onlyboolean (query)Only include contacts that have a phone when countingtrue/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:

FieldTypeDescriptionAllowed Values / ExampleRequired
X-API-Keystring (header)Authorization with APIKey<your_api_key>
workspace_idstring (path)Workspace ID3fa85f64-5717-4562-b3fc-2c963f66afa6✅
offsetinteger (query)Optional, number of rows to skip0
limitinteger (query)Optional, number of rows to return after offset (0 = all)10
keywordstring (query)Optional, keyword to search contact names and phones+18111222333
whatsapp_phonestring (query)Optional, search contacts by exact WhatsApp phone+18111222333
all_contact_label_idsstring (query)Optional, contacts must match all label IDs (comma-separated UUIDs)11111111-2222-4444-3333-555555555555,11111111-2222-4444-3333-666666666666
any_contact_label_idsstring (query)Optional, contacts match any label IDs (comma-separated UUIDs)11111111-2222-4444-3333-555555555555,11111111-2222-4444-3333-666666666666
exclude_contact_idsstring (query)Optional, exclude contacts by IDs (comma-separated UUIDs)11111111-2222-4444-3333-555555555555,11111111-2222-4444-3333-666666666666
exclude_any_contact_label_idsstring (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_idsstring (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_idsstring (query)Optional, force-include these contacts in result (comma-separated UUIDs)11111111-2222-4444-3333-555555555555,11111111-2222-4444-3333-666666666666
order_bystring (query)Optional, comma-separated list of <field>:<direction> pairs (default: created_time:desc)phone:asc,created_time:desc,name:asc
exclude_labelsstring (query)Optional, exclude by label names (comma-separated). Affects label-based filtering and countsDNC,invalid number,unreachable
whatsapp_phone_onlyboolean (query)Optional, only include contacts that match whatsapp_phonefalse
phone_onlyboolean (query)Optional, only include contacts that have a phone numberfalse

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

FieldTypeDescription
idstringUnique identifier of the event
event_typestringAlways "call.new"
affectstringAlways "add"
versionstringEvent schema version, e.g., "0.0.1"
timestampstringWhen the event occurred, in ISO 8601 format
workspace.idstringWorkspace ID associated with the event
workspace.namestringWorkspace name
source.idstringID of the event source
source.typestringChannel type (e.g., WEBCHAT, MESSENGER, etc.)
source.identifierstringIdentifier or name of the bot or integration
data.conversation_idstringID of the associated conversation
data.conversation_titlestringTitle of the conversation
data.bot_idstring (optional)ID of the bot handling the call, or null if none is assigned
data.conversation_config_idstring (optional)ID of the conversation’s configuration, or null if none
data.channelstringChannel where the call took place
data.directionstringINBOUND or OUTBOUND
data.call_fromobjectEntity initiating the call (type, id, name, address)
data.call_toobjectEntity receiving the call (type, id, name, address)
data.started_atstringTimestamp when the call started (ISO 8601 format)
data.metadataobject (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

FieldTypeDescription
idstringUnique identifier of the event
event_typestringAlways "call.ended"
affectstringAlways "delete"
versionstringEvent schema version, e.g., "0.0.1"
timestampstringWhen the event occurred, in ISO 8601 format
workspace.idstringWorkspace ID associated with the event
workspace.namestringWorkspace name
source.idstringID of the event source
source.typestringChannel type (e.g., SEAX_CALL, WHATSAPP)
source.identifierstringIdentifier or name of the bot or integration
data.conversation_idstringID of the associated conversation
data.conversation_titlestringTitle of the conversation
data.bot_idstring (optional)ID of the bot handling the call, or null if none is assigned
data.conversation_config_idstring (optional)ID of the conversation’s configuration, or null if none
data.channelstringChannel where the call took place
data.directionstringINBOUND or OUTBOUND
data.duration_secondsintegerDuration of the call in seconds
data.finish_reasonstringReason the call ended (e.g., completed, abandoned, failed)
data.call_fromobjectCaller info (type, id, name, address)
data.call_toobjectCallee info (type, id, name, address)
data.finished_atstringWhen the call ended (ISO 8601)
data.finished_bystringWho ended the call (timestamp)
data.recording_availablebooleanWhether a recording is available
data.recording_urlstring (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

FieldTypeDescription
idstringUnique identifier of the event
event_typestringAlways "call.updated"
affectstringAlways "update"
versionstringEvent schema version, e.g., "0.0.1"
timestampstringWhen the event occurred, in ISO 8601 format
workspace.idstringWorkspace ID associated with the event
workspace.namestringWorkspace name
source.idstringID of the event source
source.typestringChannel type (e.g., SEAX_CALL, WHATSAPP)
source.identifierstringIdentifier or name of the bot or integration
data.conversation_idstringID of the associated conversation
data.conversation_titlestringTitle of the conversation
data.bot_idstring (optional)ID of the bot handling the call, or null if none is assigned
data.conversation_config_idstring (optional)ID of the conversation’s configuration, or null if none
data.call_idstring (optional)ID of the call being updated
data.update_reasonstring (optional)Description of why the call was updated
data.previousobject (optional)Previous values of updated fields
data.currentobject (optional)New/current values of updated fields
data.updated_atstringWhen the update occurred (ISO 8601 format)
data.resourcesobject (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:

  1. 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.
  2. 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

FieldTypeDescription
idstringUnique identifier of the event
event_typestringAlways "meeting.ended"
affectstringAlways "add"
versionstringEvent schema version, e.g., "0.0.1"
timestampstringWhen the event occurred, in ISO 8601 format
workspace.idstringWorkspace ID associated with the event
workspace.namestringWorkspace name
source.idstringID of the event source
source.typestringChannel type (always "SEAMEET")
source.identifierstringIdentifier or name of the SeaMeet integration
data.meeting_idstringUnique identifier of the meeting
data.meeting_start_timestringWhen the meeting started (ISO 8601 format)
data.meeting_end_timestringWhen the meeting ended (ISO 8601 format)
data.duration_secondsintegerDuration of the meeting in seconds
data.transcriptionsarray of objectArray of transcription segments
data.transcriptions[].idstringUnique identifier of the transcription segment
data.transcriptions[].transcriptionstringTranscribed text content
data.transcriptions[].speakerstringName of the speaker
data.transcriptions[].anchor_startnumberStart time of the segment in seconds
data.transcriptions[].durationnumberDuration of the segment in seconds
data.summarystringAI-generated summary of the meeting
data.action_itemsarray of stringList of action items identified from the meeting
data.participantsarray of stringList of meeting participants
data.labelsarray of stringLabels associated with the meeting
data.custom_analysis_resultsobjectCustom analysis results
data.custom_analysis_results.idstringMeeting UUID for analysis
data.custom_analysis_results.datetime_utcstringMeeting datetime in UTC
data.custom_analysis_results.datetime_localstringMeeting datetime in local time
data.custom_analysis_results.timezonestringTimezone of the meeting
data.custom_analysis_results.call_directionstringDirection of the call (Inbound/Outbound)
data.custom_analysis_results.call_durationintegerCall duration in seconds
data.custom_analysis_results.agent_namestringName of the agent
data.custom_analysis_results.agent_phonestringPhone number of the agent
data.custom_analysis_results.customer_namestringName of the customer
data.custom_analysis_results.customer_phonestringPhone number of the customer
data.custom_analysis_results.topicsstringTopics discussed in the meeting
data.custom_analysis_results.labelsstringAnalysis labels
data.custom_analysis_results.summarystringSummary from analysis
data.custom_analysis_results.analysesarray of objectArray of analysis results
data.custom_analysis_results.analyses[].analysis_keystringKey for the analysis type
data.custom_analysis_results.analyses[].analysis_subkeystringSubkey for specific analysis aspect
data.custom_analysis_results.analyses[].analysis_valuestringValue/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"
}