> ## Documentation Index
> Fetch the complete documentation index at: https://docs.streamkap.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Zendesk Webhook

> Stream Zendesk events into Streamkap by registering a webhook target in Zendesk that posts to a dedicated Streamkap endpoint

## Overview

The Zendesk Webhook source receives events from your Zendesk account at a dedicated HTTPS endpoint and routes each event to a Kafka topic based on its event type (tickets, users, organizations, articles, messaging, etc.).

You point Zendesk at the Streamkap-generated webhook URL, configure which event subscriptions to send, and Streamkap takes care of parsing, key extraction, schema inference, and routing.

<Info>
  This connector is in **Beta**. Behaviors and defaults may change before general availability.
</Info>

## Prerequisites

* A Zendesk account with **Admin** permissions (required to create webhooks and event subscriptions).
* A Streamkap workspace with permission to create source connectors.
* A clear list of the Zendesk objects you want to capture (tickets, users, organizations, etc.).

## How It Works

1. **Endpoint provisioning** — When you create the source, Streamkap generates a unique HTTPS webhook URL and an API key.
2. **Zendesk subscription** — In Zendesk, you create a webhook target pointing at that URL and subscribe it to the event types you care about.
3. **Event routing** — Each incoming event carries a `type` field (for example `zen:event-type:ticket.created`). The Zendesk payload router maps the event domain to a topic — `ticket.*` events go to `ticket_events`, `user.*` events go to `user_events`, and so on.
4. **Key extraction** — The router pulls a stable identifier from the payload (typically `detail.id`) and uses it as the Kafka message key, enabling upsert-style consumption downstream.
5. **Optional flattening / fan-out** — Nested `detail` fields can be promoted to top level, and array fields (such as ticket tags or custom fields) can be fanned out into their own topics.

## Streamkap Setup

### 1. Create the Source

1. Navigate to [Sources](https://app.streamkap.com/connectors/add?tab=Sources) and choose **Zendesk Webhook**.
2. Give the source a memorable **Name** (for example, `zendesk-prod`).

### 2. Connection Settings (Auth tab)

These fields are populated automatically once you save the source — you do not enter them manually.

| Field           | Description                                                                                                         |
| --------------- | ------------------------------------------------------------------------------------------------------------------- |
| **Webhook URL** | Read-only. The HTTPS endpoint that Zendesk posts events to. Generated on save.                                      |
| **API Key**     | Read-only and encrypted. Sent by Zendesk in the `X-API-Key` header to authenticate each request. Generated on save. |

### 3. Schema (Zendesk Models)

In the **Schema** tab, the topics are pre-populated with the nine built-in Zendesk event models. Leave them all enabled, or remove any you do not want to ingest.

| Topic                       | Domain                            |
| --------------------------- | --------------------------------- |
| `ticket_events`             | Tickets                           |
| `user_events`               | Users                             |
| `organization_events`       | Organizations                     |
| `article_events`            | Help Center articles              |
| `community_post_events`     | Community posts                   |
| `messaging_events`          | Messaging tickets / conversations |
| `agent_events`              | Agent availability and status     |
| `omnichannel_config_events` | Omnichannel configuration         |
| `messaging_metrics_events`  | Messaging live metrics            |

Any fan-out topics you configure (see below) are added to this list automatically.

### 4. Settings

| Field                                     | Default         | Description                                                                                                                                                                      |
| ----------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Unknown Event Behavior**                | `DEFAULT_TOPIC` | What to do when an event type is not recognised. `DEFAULT_TOPIC` routes it to a fallback topic, `SKIP` drops it silently, `FAIL` halts the connector.                            |
| **Unknown Event Default Topic**           | `unknown`       | Topic name used when behavior is `DEFAULT_TOPIC`. Only shown when the option above is set to `DEFAULT_TOPIC`.                                                                    |
| **Flatten Detail Fields**                 | `false`         | Promote nested `detail.*` fields to top level. Recommended for destinations that do not support nested objects (most data warehouses).                                           |
| **Flatten Detail Prefix**                 | `detail_`       | Prefix added to flattened fields (for example `detail.id` becomes `detail_id`). Only shown when flattening is enabled.                                                           |
| **Include Event Field**                   | `true`          | Keep the raw `event` field in the output record. Enable for audit-log style ingestion (append mode); disable for state-table style (upsert mode) where only the entity matters.  |
| **Fan-out Fields** *(advanced)*           | *(empty)*       | Comma-separated list of `domain.field` pairs to fan out into their own topics — for example `ticket.tags,ticket.custom_fields,organization.tags`. See [Fan-out](#fan-out) below. |
| **Enable Dead Letter Queue** *(advanced)* | `false`         | Failed records are written to a DLQ topic instead of crashing the connector.                                                                                                     |

### 5. Save and Copy the Endpoint

Save the source. Copy the **Webhook URL** and **API Key** — you will need them in the next section to configure Zendesk.

## Zendesk Setup

You configure Zendesk to send events to the Streamkap endpoint in two pieces: a **webhook target** and one or more **event subscriptions**.

### 1. Create the Webhook in Zendesk

1. In Zendesk, go to **Admin Center → Apps and integrations → Webhooks → Webhooks**.
2. Click **Create webhook**.
3. Choose **Trigger or automation** *(or **Zendesk events** — see step 2 below)*.
4. Configure the webhook:
   * **Name** — `Streamkap` (or anything memorable).
   * **Endpoint URL** — paste the **Webhook URL** from Streamkap.
   * **Request method** — `POST`.
   * **Request format** — `JSON`.
   * **Authentication** — choose **API key**:
     * **Header name** — `X-API-Key`
     * **Value** — paste the **API Key** from Streamkap.
5. Click **Test webhook** to confirm Zendesk can reach the endpoint, then **Create webhook**.

### 2. Subscribe to Event Types

To stream object-level events (ticket created, user updated, organization deleted, …), connect the webhook to Zendesk's **Events** subscription system:

1. In **Admin Center**, go to **Apps and integrations → Webhooks → Connections**.
2. Click **Create connection**, pick the webhook you just created, and select the **event types** you want to forward.
3. Use event types from the families that match the topics you enabled in Streamkap:
   * `zen:event-type:ticket.*`
   * `zen:event-type:user.*`
   * `zen:event-type:organization.*`
   * `zen:event-type:article.*`
   * `zen:event-type:community_post.*`
   * `zen:event-type:messaging_ticket.*`
   * `zen:event-type:agent.*`
   * `zen:event-type:omnichannel_config.*`
   * `zen:event-type:messaging_live_metrics.*`
4. Save the connection. Zendesk now begins POSTing events to Streamkap.

<Info>
  For triggers and automations (instead of native event subscriptions), set the request body to forward the relevant fields — including a `type` value such as `zen:event-type:ticket.updated` — so the payload router can identify the event family.
</Info>

## Event Routing Reference

The payload router inspects the `type` field on each incoming event and maps it to a topic.

| Event type prefix                         | Topic                       | Default key                          |
| ----------------------------------------- | --------------------------- | ------------------------------------ |
| `zen:event-type:ticket.*`                 | `ticket_events`             | `{ "detail_id": <ticket_id> }`       |
| `zen:event-type:user.*`                   | `user_events`               | `{ "detail_id": <user_id> }`         |
| `zen:event-type:organization.*`           | `organization_events`       | `{ "detail_id": <organization_id> }` |
| `zen:event-type:article.*`                | `article_events`            | `{ "detail_id": <article_id> }`      |
| `zen:event-type:community_post.*`         | `community_post_events`     | `{ "detail_id": <post_id> }`         |
| `zen:event-type:messaging_ticket.*`       | `messaging_events`          | `{ "detail_id": <message_id> }`      |
| `zen:event-type:agent.*`                  | `agent_events`              | `{ "detail_agent_id": <agent_id> }`  |
| `zen:event-type:omnichannel_config.*`     | `omnichannel_config_events` | `{ "account_id": <account_id> }`     |
| `zen:event-type:messaging_live_metrics.*` | `messaging_metrics_events`  | `{ "account_id": <account_id> }`     |

Event types ending in `.deleted` or `.removed` (but not `.soft_deleted` or `.undeleted`) automatically set `__deleted: true` on the output record so downstream sinks can issue tombstones.

## Fan-out

When **Flatten Detail Fields** is on, scalar fields are promoted to the top level — but arrays and lists of objects (tags, custom fields, comments, …) cannot be flattened cleanly. Fan-out solves this by emitting one record per array element to a dedicated topic.

Set **Fan-out Fields** to a comma-separated list of `domain.field` pairs:

```
ticket.tags,ticket.custom_fields,ticket.comments,ticket.collaborators,ticket.followers,organization.tags
```

| Fan-out entry          | Generated topic        | Key shape              |
| ---------------------- | ---------------------- | ---------------------- |
| `ticket.tags`          | `ticket_tags`          | `{ detail_id, value }` |
| `ticket.custom_fields` | `ticket_custom_fields` | `{ detail_id, id }`    |
| `ticket.comments`      | `ticket_comments`      | `{ id }`               |
| `ticket.collaborators` | `ticket_collaborators` | `{ detail_id, id }`    |
| `ticket.followers`     | `ticket_followers`     | `{ detail_id, id }`    |
| `organization.tags`    | `organization_tags`    | `{ detail_id, value }` |

The fan-out topics are added to the Schema list automatically, so you do not need to register them manually.

Each fan-out record contains the array element's own fields plus:

* `detail_id` — parent entity ID (matches the key field name).
* `_ctx_event_id` — the originating webhook event UUID, useful for correlating fan-out records back to the parent event.

Example fan-out tag record:

```json theme={null}
{
  "value": "urgent",
  "detail_id": 987654,
  "_ctx_event_id": "01HF6...-event-uuid"
}
```

<Warning>
  Fan-out topics do **not** emit tombstone records when array items are removed (for example when a tag is removed from a ticket). To handle deletions, either configure the downstream sink for delete-and-reinsert mode on each parent event, or treat each ticket event as the source of truth and full-replace the child rows.
</Warning>

## Example Payload

A typical event sent by Zendesk:

```json theme={null}
{
  "type": "zen:event-type:ticket.created",
  "id": "01HF6...-event-uuid",
  "account_id": 123456,
  "time": "2026-01-24T15:30:00Z",
  "subject": "zen:ticket:987654",
  "detail": {
    "id": 987654,
    "subject": "Cannot login",
    "status": "new",
    "tags": ["urgent", "billing"],
    "custom_fields": [{ "id": 123, "value": "tier1" }]
  },
  "event": {
    "comment": { "id": 555, "body": "We are looking into it" }
  }
}
```

With **Flatten Detail Fields** enabled and **Include Event Field** disabled, the record written to `ticket_events` looks like:

```json theme={null}
{
  "type": "zen:event-type:ticket.created",
  "account_id": 123456,
  "time": "2026-01-24T15:30:00Z",
  "subject": "zen:ticket:987654",
  "detail_id": 987654,
  "detail_subject": "Cannot login",
  "detail_status": "new",
  "__deleted": false
}
```

The arrays (`tags`, `custom_fields`) are emitted to `ticket_tags` and `ticket_custom_fields` if those entries are listed in **Fan-out Fields**.

## Common Patterns

### State table (upsert) ingestion

Use this when you want one row per Zendesk entity in your warehouse, mirroring the live state.

* **Flatten Detail Fields** — `true`
* **Include Event Field** — `false`
* **Fan-out Fields** — list any nested arrays you want as separate tables (typically `ticket.tags`, `ticket.custom_fields`)

Downstream sinks upsert by the `detail_id` key. `.deleted` events flip `__deleted: true`, which most sinks treat as a tombstone.

### Audit log ingestion

Use this when you want every event preserved as a separate row.

* **Flatten Detail Fields** — `false`
* **Include Event Field** — `true`
* **Fan-out Fields** — empty

Each event becomes a record. Downstream consumers see the full context of every change.

## Troubleshooting

**Zendesk's webhook test returns 401** — Check that the `X-API-Key` header name is exactly `X-API-Key` and the value matches the API Key shown in the Streamkap UI. Regenerate the source if the key was lost.

**Events arrive in the `unknown` topic instead of the expected one** — The event `type` did not match any known prefix. Verify the event family is one of those listed in [Event Routing Reference](#event-routing-reference). If you are using a Zendesk trigger (rather than an event subscription), make sure the request body includes a valid `type` value.

**No data is flowing** — Confirm in Zendesk that the webhook is **Active** and connected to at least one event subscription. Use the **Activity** view on the Zendesk webhook to see recent delivery attempts and response codes.

**Records are nested when I expected flat fields** — Toggle **Flatten Detail Fields** on and re-deploy the source. Existing records already in Kafka will not be retroactively flattened.

**My destination cannot handle the array fields** — Add the relevant entries to **Fan-out Fields** so each array element becomes its own record in a dedicated topic.

## Limitations

* The Zendesk Webhook source is currently **Beta**.
* The connector accepts only `POST` requests; do not configure Zendesk to use a different HTTP method.
* Maximum payload size is 50 MB and maximum header size is 64 KB. Zendesk events fit comfortably within these limits.
* Each request is treated as a single Kafka record. Zendesk does not batch events into one request.
* The connector runs as a single task; horizontal scaling requires multiple source instances.

## See Also

* [Webhook Source](/webhook) — generic webhook source for any HTTP-capable producer.
