Channel Webhooks

This guide explains how to integrate with ChatWerk channel webhooks.

What channel webhooks are

Channel webhooks let ChatWerk send HTTP POST notifications to your endpoint whenever selected events happen on a specific channel. This is probably one the most important feature you are looking for.

Typical use cases:

  • synchronize conversations or contacts into an external system
  • trigger workflows after inbound or outbound messages
  • react to status changes such as message delivery state or conversation assignment
  • keep a lightweight near-real-time mirror of channel activity

How the integration works

  1. Your system exposes an HTTPS endpoint that accepts JSON POST requests.
  2. You create a webhook for a specific channel in ChatWerk.
  3. You choose either:
    • all events with all_events: true
    • a selected subset with events: [...]
  4. ChatWerk sends matching events to your endpoint.
  5. Your endpoint validates the secret header, parses the payload, processes it idempotently, and returns a 2xx response quickly.

Management API

Channel webhooks are managed per channel.

Available endpoints:

Access requirements:

  • the caller must be authenticated
  • the caller must have access to the specific channel

Step-by-step setup

1. Prepare your receiver endpoint

Your endpoint should:

  • accept POST requests with Content-Type: application/json
  • validate the x-webhook-secret header
  • tolerate duplicate deliveries
  • process optional fields defensively
  • return 2xx as soon as the payload is safely accepted

Minimal example:

POST /integrations/chatwerk/channel-events HTTP/1.1
Content-Type: application/json
x-webhook-secret: 3d9f...
user-agent: chatwerk/1.0

With payloads described below.

2. Create a webhook

Example request: POST /core/channels/channel-00/webhooks

{
  "url": "https://example.com/integrations/chatwerk/channel-events",
  "headers": {
    "X-Partner-Id": "acme"
  },
  "enabled": true,
  "events": [
    "message_received",
    "message_status_changed",
    "conversation_created"
  ]
}

Example response:

{
  "id": "channelWh0",
  "url": "https://example.com/integrations/chatwerk/channel-events",
  "secret": "generated-secret",
  "headers": {
    "X-Partner-Id": "acme"
  },
  "enabled": true,
  "created_at": "2026-06-09T10:00:00Z",
  "updated_at": "2026-06-09T10:00:00Z",
  "events": [
    "message_received",
    "message_status_changed",
    "conversation_created"
  ]
}

Notes:

  • secret is generated by ChatWerk on creation
  • the secret is sent back in webhook deliveries as x-webhook-secret
  • custom headers are forwarded as configured
  • if you omit events, the current implementation falls back to all_events: true

3. Store the secret securely

Treat the returned secret like a credential:

  • store it encrypted or in a secret manager
  • compare it exactly on every incoming request
  • never rely only on source IP

If you need additional authentication, configure extra static headers in the webhook definition and validate them too.

4. Test the payload shape

Use the preview endpoint to inspect example payloads before enabling production processing:

GET /core/channel_webhooks/preview?event_type=message_received
GET /core/channel_webhooks/preview?event_type=conversation_created
GET /core/channel_webhooks/preview?event_type=com_channel_created

This is useful for:

  • bootstrapping parsers
  • checking which top-level objects are present for a given event type
  • generating internal fixtures

5. Enable processing in production

Once your receiver validates the secret and successfully parses the payload:

  • keep the webhook enabled: true
  • monitor non-2xx responses
  • log event IDs for traceability

Webhook request format

ChatWerk sends POST requests with:

  • header Content-Type: application/json
  • header user-agent: chatwerk/1.0
  • header x-webhook-secret: <webhook-secret>
  • any custom headers configured in the webhook

The target URL may include query parameters. They are preserved when ChatWerk calls the endpoint.

Payload structure

Top-level payload:

{
  "id": "event-id",
  "type": "message_status_changed",
  "channel_id": "channel-01",
  "contact": {},
  "com_channel": {},
  "conversation": {},
  "gateway": {},
  "message": {},
  "message_status": {},
  "timestamp": "2026-06-09T10:00:00Z"
}

Important behavior:

  • id is the event ID; use it for deduplication
  • type is the event name
  • channel_id is always included for channel webhooks
  • message_status is currently populated for message_status_changed
  • message, conversation, com_channel, contact, and gateway are optional and depend on the event

Example payloads

Message status changed

{
  "id": "some_id",
  "type": "message_status_changed",
  "channel_id": "channel-01",
  "com_channel": {
    "id": "0000000000",
    "messenger_id": "some-messenger-id",
    "type": "person"
  },
  "conversation": {
    "id": "0000000000",
    "blocked": false,
    "opted_in": false,
    "created_at": "0001-01-01T00:00:00Z"
  },
  "gateway": {
    "id": "0000000000"
  },
  "message": {
    "id": "msg-id",
    "type": "text",
    "direction": "in",
    "text": "[email protected]",
    "author_id": "testUser1",
    "timestamp": "2024-01-01T00:00:00Z"
  },
  "message_status": {
    "id": "msg-id",
    "status": "read"
  },
  "timestamp": "2026-06-09T10:00:00Z"
}

Message received

Representative shape:

{
  "id": "some_id",
  "type": "message_received",
  "channel_id": "channel_id",
  "com_channel": {
    "id": "com_channel_id",
    "messenger_id": "messenger_id",
    "name": "name",
    "attributes": {
      "key": "value"
    },
    "type": "person",
    "overridden_name": true,
    "notes": "some notes"
  },
  "conversation": {
    "id": "conversation_id",
    "labels": [
      "label_1",
      "label_2"
    ],
    "blocked": true,
    "opted_in": true
  },
  "gateway": {
    "id": "gateway_id",
    "messenger_id": "messenger_id",
    "provider": "WhatsApp",
    "messenger_type": "WhatsApp"
  },
  "message": {
    "id": "message_id",
    "author_id": "author_id",
    "type": "image",
    "direction": "in",
    "text": "some text",
    "labels": [
      "some",
      "label"
    ],
    "data": {
      "media": [
        {
          "url": "https://signed.example/media",
          "mime_type": "image/gif",
          "status": "synced"
        }
      ],
      "error": {
        "text": "error msg",
        "code": "error code"
      },
      "location": {
        "latitude": 54.89587784,
        "longitude": 19.15745544
      },
      "items": [
        {
          "payload": "item payload",
          "text": "item text",
          "description": "item description"
        }
      ],
      "pressed_buttons": [
        {
          "payload": "some payload",
          "text": "any"
        }
      ]
    },
    "timestamp": "2026-06-09T10:00:00Z"
  },
  "timestamp": "2026-06-09T10:00:00Z"
}

Supported event types

The current implementation accepts the following channel webhook events.

Message events

  • message_received: incoming message received on the channel
  • message_sent: outbound message sent from ChatWerk
  • message_edited: existing message content changed
  • message_deleted: message removed
  • message_reacted: reaction added to a message
  • message_unreacted: reaction removed from a message
  • message_blocked: message blocked
  • message_status_changed: delivery or read status changed
  • message_media_synchronized: media became available in synchronized storage
  • message_media_synchronization_error: media synchronization failed
  • message_media_transformed: media was transformed
  • message_labels_changed: message labels changed
  • button_message_replied: interactive button reply received

Conversation events

  • conversation_created: conversation created
  • conversation_deleted: conversation deleted
  • conversation_opened: conversation opened
  • conversation_closed: conversation closed
  • conversation_read: conversation marked as read
  • conversation_unread: conversation marked as unread
  • conversation_blocked: conversation blocked
  • conversation_unblocked: conversation unblocked
  • conversation_assigned: conversation assigned to a user
  • conversation_unassigned: conversation unassigned
  • conversation_labels_changed: conversation labels changed
  • conversation_opted_in: conversation opted in
  • conversation_opted_out: conversation opted out
  • conversation_imported: conversation imported

Communication channel events

  • com_channel_created: communication channel created
  • com_channel_deleted: communication channel deleted
  • com_channel_name_changed: communication channel name changed
  • com_channel_notes_changed: communication channel notes changed
  • com_channel_email_changed: communication channel email changed
  • com_channel_phone_changed: communication channel phone changed
  • com_channel_mid_changed: messenger identifier changed
  • com_channel_attributes_changed: communication channel attributes changed

Marketing and invitation events

  • marketing_opted_in: marketing consent granted
  • marketing_opted_out: marketing consent revoked
  • clear_marketing_opted_in: marketing opt-in state cleared
  • marketing_opt_in_question: opt-in question flow triggered
  • conversation_invited: number upload invitation sent
  • conversation_invitation_accepted: invitation accepted
  • conversation_invitation_rejected: invitation rejected

Event selection strategy

Use narrow subscriptions when possible.

Recommended patterns:

  • CRM sync: com_channel_*, conversation_created, message_received
  • delivery tracking: message_sent, message_status_changed
  • bot or workflow triggering: message_received, button_message_replied
  • conversation operations: conversation_created, conversation_opened, conversation_closed, conversation_assigned

Avoid subscribing to all events unless you really need them. It increases processing volume and makes versioning harder on your side.

Potential problems and how to handle them

1. Duplicate deliveries

The sender uses retries. Your endpoint may receive the same event more than once.

How to handle it:

  • deduplicate by payload id
  • make handlers idempotent
  • store processed IDs for an appropriate retention period

2. Slow endpoint responses

The sender timeout is currently 5 seconds and the sender retries up to 3 times.

How to handle it:

  • acknowledge quickly with 2xx
  • move heavy work to an internal queue
  • avoid synchronous calls to slow downstream systems

3. Delivery is best effort

Failed webhook calls are logged by the sender, but they are not escalated into a replay mechanism in the channel webhook sender itself.

How to handle it:

  • monitor your availability and error rates
  • alert on non-2xx responses
  • persist raw webhook payloads on your side for troubleshooting
  • if data completeness matters, build a reconciliation process against your source of truth

4. Changes may not apply immediately

Webhook definitions are cached by channel for up to 1 minute in the sender process.

What this means:

  • a newly created webhook may not start receiving events instantly
  • a deleted or disabled webhook may still receive events for a short period
  • event filter changes may take up to 1 minute to fully propagate

How to handle it:

  • expect short propagation delay after create, update, delete, enable, or disable
  • do not treat one or two transitional events as a bug immediately

5. Optional fields are not guaranteed

Not every payload contains every object. The payload depends on the event type and available context.

How to handle it:

  • treat message, conversation, contact, com_channel, gateway, and message_status as optional
  • validate only the fields required by your use case
  • use tolerant JSON parsing

6. Media URL availability

Media URLs are only attached when media is already synchronized and a signed URL can be generated. In practice, do not assume every media item includes a usable url.

How to handle it:

  • check message.data.media[].status
  • treat missing url as a valid case
  • retry your own media fetch logic only if your business process requires it

7. Preview payloads vs live payloads

The preview endpoint is intentionally illustrative. It helps with schema discovery, but it is not a contract that every example field will always appear in live traffic.

One current implementation detail worth knowing:

  • preview examples may show richer media objects than the live sender currently populates

How to handle it:

  • use preview payloads for onboarding
  • validate your parser against real deliveries before going live

8. URL validation during configuration

Webhook create and update requests validate that the target URL is reachable.

How to handle it:

  • expose an externally reachable endpoint before creating the webhook
  • verify DNS, TLS, firewall, and routing first
  • expect 422 with url_reachable if the URL cannot be reached

Error cases when managing webhooks

Typical API responses:

  • 401: unauthenticated request
  • 403: authenticated user without write permission
  • 404: webhook not found on update or delete
  • 422: invalid request data

Known validation errors:

  • url_reachable: the configured URL is not reachable
  • notification_events: at least one event type is invalid

Good practices

  • use one webhook endpoint per integration purpose when responsibilities differ
  • validate x-webhook-secret before parsing deeply
  • log event id, type, and channel_id
  • process asynchronously
  • design for retries and duplicates
  • keep event subscriptions as narrow as possible
  • version your internal mapping from ChatWerk events to your domain events
  • keep a dead-letter or quarantine path for malformed or unsupported payloads
  • test with the preview endpoint and then with live deliveries

Recommended receiver flow

  1. Receive the request.
  2. Verify x-webhook-secret.
  3. Parse JSON.
  4. Reject unsupported channels or event types if needed.
  5. Deduplicate by event id.
  6. Persist the raw payload for audit/debugging.
  7. Enqueue business processing.
  8. Return 200 OK.

Summary

Channel webhooks are a straightforward push integration for channel-level events. The important implementation constraints are:

  • secret-based header validation
  • optional and event-dependent payload sections
  • retries and duplicate tolerance
  • best-effort delivery
  • up to 1 minute propagation delay for webhook changes

If you build your receiver around idempotency, fast acknowledgements, and good observability, the integration is robust and simple to operate.


Did this page help you?