Zammad Integration
The Zammad integration is a read-only connection for service-desk reporting. It supports Zammad Cloud and self-hosted Zammad instances that are reachable from the public internet over HTTPS.
Private or firewall-only Zammad instances are not supported in this release. The Zammad integration does not run through the Resplendent Sync Agent.
Before you begin
Section titled “Before you begin”You need:
- A dedicated Zammad service user. Avoid tying the connection to a person’s account.
- Agent access to every Zammad group whose tickets should be available to Resplendent.
- The
ticket.agentpermission for core ticket access. - The
user_preferences.access_tokenpermission while creating the token. - A public Zammad Instance URL with a certificate trusted by standard public certificate authorities.
The integration never creates, updates, or deletes records in Zammad.
Permissions
Section titled “Permissions”Give the service user’s role only the permissions needed for the Datasets you plan to sync.
| Permission | Required? | What it enables |
|---|---|---|
ticket.agent |
Yes | Core ticket, tag, user, organization, article, time-accounting, state, and priority access |
admin.group |
Optional | Groups Dataset |
admin.sla |
Optional | SLAs Dataset |
admin.role |
Optional | Roles Dataset |
admin.calendar |
Optional | Calendars Dataset |
admin.audit_log |
Optional | Audit Logs Dataset |
The service user also needs read access to the appropriate Zammad groups. Zammad only returns records that the token user is allowed to see.
Missing an optional permission does not break the connection or other Datasets. Resplendent shows a warning on the affected Groups, SLAs, Roles, Calendars, or Audit Logs Dataset instead.
You do not need admin.object. Custom fields returned by the normal Tickets, Users, and Organizations APIs are discovered automatically.
See Zammad’s documentation for the current permission reference and group access levels.
Create the service user and access token
Section titled “Create the service user and access token”1. Create a dedicated role and user
Section titled “1. Create a dedicated role and user”- In Zammad, open Admin → Manage → Roles.
- Create a role for Resplendent and grant
ticket.agent. - Add
admin.group,admin.sla,admin.role,admin.calendar, oradmin.audit_logonly if you want the corresponding optional Dataset. - Give the role read access to the Zammad groups that should be included in reporting.
- Create or select a dedicated service user and assign the role.
If the service user cannot open a ticket in the Zammad interface, its access token will not be able to sync that ticket either.
2. Create an API access token
Section titled “2. Create an API access token”- Sign in to Zammad as the service user.
- Open the user’s profile from the avatar menu and select Token Access.
- Create a token named
Resplendent Data. - Select
ticket.agentand any optional permissions you chose above. - Set an expiration date that matches your credential-rotation policy.
- Copy the token when Zammad displays it. Zammad displays a newly created token only once.
Resplendent uses Zammad’s HTTP access-token authentication. Do not provide a Zammad password or an OAuth token. For more detail, see Zammad’s API authentication guide and access-token reference.
Connect Zammad to Resplendent Data
Section titled “Connect Zammad to Resplendent Data”- In Resplendent Data, go to Settings → Integrations.
- Find Zammad and select Connect.
- Enter the two fields shown in the setup form:
- Instance URL: the public HTTPS root of your Zammad instance, such as
https://support.example.com. Do not include/api/v1, credentials, a query string, or a fragment. - Access Token: the token created for the service user.
- Instance URL: the public HTTPS root of your Zammad instance, such as
- Test and save the connection.
- Select the Datasets and columns you want to sync.
The connection test makes a small Tickets request. A successful test proves that the token is valid and can read the core ticket API. Each selected Dataset then runs independently and reports its own permission or response error.
The Instance URL must remain on the same HTTPS origin if Zammad redirects an API request. Its DNS records must resolve only to public network addresses. Private, loopback, link-local, and embedded-credential destinations are rejected.
Datasets and default cadence
Section titled “Datasets and default cadence”These are the defaults requested by the integration. Your Resplendent subscription tier can impose a longer effective minimum interval.
| Dataset | Core or optional | Daytime sync | Nightly replacement |
|---|---|---|---|
| Tickets | Core | Every 5 minutes, incremental | Yes |
| Ticket Articles | Core | Every 5 minutes, incremental | Yes |
| Ticket Tags | Core | Every 5 minutes, incremental | Yes |
| Ticket History | Core | Every 5 minutes, incremental | Yes |
| Time Accounting | Core | Every 15 minutes, incremental | Yes |
| Users | Core | Every 60 minutes, incremental | Yes |
| Organizations | Core | Every 60 minutes, incremental | Yes |
| Ticket States | Core | Every 1,440 minutes, full | Yes |
| Ticket Priorities | Core | Every 1,440 minutes, full | Yes |
| Groups | Optional; admin.group |
Every 1,440 minutes, full | Yes |
| SLAs | Optional; admin.sla |
Every 1,440 minutes, full | Yes |
| Roles | Optional; admin.role |
Every 1,440 minutes, full | Yes |
| Calendars | Optional; admin.calendar |
Every 1,440 minutes, full | Yes |
| Audit Logs | Optional; admin.audit_log |
Every 60 minutes, incremental | Yes |
Initial history is downloaded in bounded, resumable batches. Incremental Datasets return one configured batch per job, up to Zammad’s 100-record page limit. Ticket Tags uses that setting as the number of tagged tickets scanned, retaining all associations for those tickets. Ticket History similarly retains every direct history event for each ticket in its batch. These batches can therefore contain more child rows than tickets. Initial and historical Ticket Tags reads use Zammad’s tags:* search, so untagged tickets are skipped before their tag lists are requested. Tickets, Users, Organizations, Ticket Tags, and Ticket History seed their first batch from the most recently updated records, then backfill in ascending source ID order. Each later batch resumes strictly after its saved ID, so deleting earlier records or removing their tags does not shift the next batch. Older page cursors restart the ID backfill once on upgrade; repeated rows merge by primary key. Daytime incremental reads use a small timestamp overlap and primary-key deduplication so records sharing the same update time are retained. Requests use bounded ticket-level concurrency so large instances do not create an unbounded request burst.
Manual and nightly resyncs repeat the same first batch. The first successful write replaces the Dataset, then history continues in later jobs through the configured history limit. Resyncs do not download every page in one job. The small lookup Datasets use full snapshots on each refresh and do not crawl history.
Calendars are the business-hour calendars Zammad uses for SLA calculation. Each row includes the timezone, weekday hours, public holidays, and iCal URL. The SLAs Dataset stores the related Calendar ID.
Ticket deletion and lifecycle behavior
Section titled “Ticket deletion and lifecycle behavior”During the day, changed records are added or updated. Hard deletions converge through independent nightly replacements:
- Tickets rebuilds from the current Tickets endpoint.
- Ticket Articles separately rebuilds from the current Ticket Articles endpoint.
- Ticket Tags separately rebuilds by reading the current tag list for each ticket in its history scope.
- Ticket History separately rebuilds from current histories of tickets in its history scope.
- Time Accounting separately rebuilds from the current Time Accounting endpoint.
The Datasets do not inspect or modify one another. Their nightly crawls can finish at different times, so temporary partial results and cross-Dataset mismatches are expected while history reloads. Configure enough history to cover the records you need. If a later history job fails, the batches already written remain available and the next retry resumes the saved source boundary.
A closed ticket is not a deleted ticket. Closed tickets, inactive users, inactive organizations, and inactive lookup values are retained while Zammad returns them within the configured sync scope.
Ticket tag changes touch their related ticket in Zammad. Daytime Ticket Tags syncs therefore fetch the current tag associations for tickets inside the timestamp overlap window. The tags:* filter is limited to initial and historical reads; incremental queries still include changed tickets that have lost their last tag. A removed or renamed association can remain temporarily until Ticket Tags rebuilds during its next resync; it does not inspect or mutate the stored Tickets Dataset.
Ticket Tags
Section titled “Ticket Tags”Ticket Tags contains one row per ticket-and-tag association. Each row has a deterministic relationship ID, the source Ticket ID, the tag text, and the related ticket’s update timestamp. The Ticket ID and tag columns link to the corresponding ticket in Zammad.
Tag text is limited to 32,000 Unicode characters. Longer values retain a visible ellipsis inside the limit and set tag_truncated; original_tag_length records the source length. The relationship ID is derived from the complete source value, so truncation cannot merge two distinct tags.
Ticket History
Section titled “Ticket History”Ticket History contains one row per direct ticket event from GET /api/v1/ticket_history/{ticket_id}. Select it when adding Datasets to a Zammad source; it uses the same ticket.agent permission and group read access as Tickets. Entries include ticket creation, state, owner, group, priority, and other attribute changes. Related article events and the response’s embedded assets are excluded.
| API field / derived field | Dataset column | Purpose |
|---|---|---|
id, ticket_id |
History ID, Ticket ID | Stable event identity and parent ticket |
type, attribute |
Event Type, Changed Attribute | Kind of event and changed field; state changes use attribute = 'state' |
id_from, id_to |
Previous Value ID, New Value ID | Stable IDs for changes such as state, owner, group, or priority |
value_from, value_to |
Previous Value, New Value | Readable values recorded at the time of the change |
created_at |
Changed At | Original event timestamp in UTC, used for duration calculations |
created_by_id |
Changed By User ID | User who made the change |
sourceable_type, sourceable_id, sourceable_name |
Change Source Type, Change Source ID, Change Source Name | Trigger, scheduler, or other recorded change source |
ticket_created_at |
Ticket Created At | Parent creation timestamp, used to reconstruct its initial status interval |
ticket_updated_at |
Ticket Updated At | Parent update timestamp used only as the incremental watermark |
The initial job reads one configured batch of the most recently updated tickets, capped at 100, and keeps their complete direct histories. This seeds a recent parent watermark so the first incremental job does not replay weeks of unrelated updates. Historical jobs then backfill in ascending Ticket ID order from the beginning, keeping complete histories for each batch. The batch size counts tickets rather than event rows. Later jobs resume strictly after the last fetched Ticket ID; a batch with no history events still advances the saved parent cursor. Manual and nightly resyncs restart the same bounded crawl and replace the first batch before refilling the configured history scope.
Every five minutes, incremental sync reads one configured batch of changed tickets, capped at 100, within a fixed update-time window with a five-minute overlap. It rereads each ticket’s complete direct history and merges by History ID. Progress is saved after a successful warehouse write, including batches with no direct events. Later jobs resume strictly after the last processed Ticket ID in the same window. Once that window is exhausted, the next one starts from its saved completion time; a newer watermark from historical backfill cannot skip pending changes. Large change windows catch up across several polls. This captures multiple state changes between polls and deduplicates replayed events without replacing their original Changed At timestamps. Closed tickets are included. Standalone history entries that do not update their parent ticket converge on the next resync, as do events removed by source deletion; fetching historical rows does not modify the stored Tickets Dataset.
To measure time in a status, filter attribute = 'state' and order events for each ticket by Changed At, then History ID. Each New Value ID starts an interval ending at the next state-change timestamp. Use the current time for an interval that is still open, and sum separate intervals when a ticket returns to the same status. For the initial interval before the first change, use Ticket Created At and the first event’s Previous Value ID. If there are no state changes, join the current state from Tickets. These are elapsed durations; business-hours durations require the appropriate calendar.
Audit Logs
Section titled “Audit Logs”Select Audit Logs when adding Datasets to a Zammad source. The service user’s role and API token need admin.audit_log. This optional Dataset requires a Zammad version exposing GET /api/v1/audit_logs; preview shows a warning when the route is unavailable or the token lacks permission.
Audit Logs records administrative and security changes, including the actor, action, affected object, previous and new values, and source IP. Ticket history is a separate Zammad resource. Background-job entries can have empty User ID, User Full Name, and Source IP values.
| API field | Dataset column | Contents |
|---|---|---|
id |
Audit Log ID | Stable primary key for the entry |
user_id, user_fullname |
User ID, User Full Name | Actor at the time of the change, when available |
action_type |
Action Type | Creation, update, deletion, or session takeover action |
auditable_type, auditable_type_label |
Auditable Type, Auditable Type Label | Object class and its readable label |
auditable_id, auditable_name |
Auditable ID, Auditable Name | Identifier and name of the affected object |
value_from, value_to |
Value From, Value To | Previous and new audited attributes, serialized as JSON text |
source_ip, preferences |
Source IP, Preferences | Request source and entry metadata; preferences are JSON text |
created_at, updated_at |
Created At, Updated At | UTC entry timestamps; Updated At is the incremental watermark |
Audit entries are append-only. Initial and historical reads use ascending ID pages and honor the configured batch size, capped at 100 entries. Hourly incremental reads scan newest entries first and stop once they pass the watermark’s five-minute overlap. Manual and nightly resyncs restart with the first configured batch and refill the configured history scope. Zammad removes entries older than 12 months; the next resync removes those expired entries from the Dataset.
Ticket Articles and sensitive content
Section titled “Ticket Articles and sensitive content”Ticket Articles includes both public replies and internal notes that the service user is allowed to see. The internal column distinguishes the two. Restrict the service user’s group access and select Dataset columns deliberately because internal notes may contain sensitive information.
Article content is selectable rather than mandatory:
bodycontains bounded source content.body_textcontains a safely derived plain-text representation for search and analysis.body_truncatedandoriginal_body_lengthdescribe truncation ofbody.body_text_truncatedandoriginal_body_text_lengthdescribe truncation ofbody_text.
Both content columns have a limit of 32,000 Unicode characters. When content exceeds the limit, Resplendent keeps a visible ellipsis within the 32,000-character value and sets the corresponding truncation flag.
If you do not want message content stored in Resplendent, omit body and body_text when selecting Ticket Articles columns. You can still analyze metadata such as direction, sender, recipient, subject, content type, timestamps, and whether an article is internal.
Attachment handling is metadata-only. Resplendent can retain attachment ID, filename, size, content type, and attachment count. The Ticket Articles Dataset does not create an attachment URL or link column in this release. It does not download or store attachment files, inline images, base64 payloads, or other attachment contents. Remote HTML is parsed as text without running scripts, loading embedded resources, or rendering remote content.
Request and retry behavior
Section titled “Request and retry behavior”Each API request has one 30-second end-to-end deadline covering DNS resolution, validated-address failover, redirects, retry delays, and response streaming, with at most four attempts inside that deadline. JSON response bodies are streamed through a 16 MiB limit before decoding. For throttling and transient Zammad server failures, Resplendent honors a numeric Retry-After value within the remaining deadline and the 30-second delay cap. When that header is absent or invalid, it uses bounded exponential backoff with jitter.
After the bounded attempts are exhausted, the sync stops with an actionable rate-limit, remote-server, or network error. A later scheduled sync retries the failed page without writing an incomplete batch.
Troubleshooting
Section titled “Troubleshooting”| Error category | What it means | What to do |
|---|---|---|
| Invalid credentials | Zammad rejected the Access Token, usually with HTTP 401. | Create or rotate the service user’s API access token, then update Access Token in Resplendent. Do not use a password or OAuth bearer token. |
| Missing core permission | The token cannot read Tickets and the connection reports that ticket.agent is required. |
Add ticket.agent to the service user’s role and token, confirm group read access, then test again. |
| Missing optional permission | Groups, SLAs, Roles, Calendars, or Audit Logs shows a Dataset warning naming admin.group, admin.sla, admin.role, admin.calendar, or admin.audit_log. |
Add only that permission if the Dataset is needed. Otherwise leave the optional Dataset unselected. Core syncs continue. |
| Unsafe URL | The URL is not HTTPS, contains credentials/query/fragment data, resolves to a non-public address, or redirects to another origin. | Enter the public root URL only. Private-network and firewall-only instances are unsupported in this release. |
| TLS failure | The certificate could not be validated. | Install a certificate with a complete chain trusted by public certificate authorities. Certificate verification cannot be disabled. |
| Network failure | DNS did not resolve, the public host could not be reached, or requests timed out after bounded retries. | Confirm public DNS, firewall rules, and Zammad availability from outside the Zammad network. |
| Throttling | Zammad kept returning HTTP 429 after Resplendent honored bounded retry delays. | Reduce competing API traffic, review Zammad capacity, and let the next scheduled sync retry. |
| Unsupported response shape | Zammad returned invalid JSON, a non-array Dataset response, or fields that do not match the supported schema. | Record the Dataset name, Zammad version, timestamp, and safe error text, then contact support. Do not send the response body if it contains ticket content. |
| Transient Zammad failure | Zammad kept returning HTTP 5xx after bounded retries. | Check Zammad health and server logs, then retry after the incident. Previously written batches remain available. |
Safe information to collect for support
Section titled “Safe information to collect for support”Share the affected Dataset, approximate timestamp, Zammad version, Resplendent source identifier, and the exact safe error category. Never share the Access Token, an Authorization header, article bodies, attachment contents, or a raw API response containing customer data.
Security notes
Section titled “Security notes”- The Access Token is stored through Resplendent’s encrypted credential-key path and is not returned in visible connection metadata.
- API requests are read-only and keep the Authorization header on the configured origin.
- User-facing errors describe the failure category without copying Zammad response bodies.
- Operational sync logs contain bounded identifiers and counts, not article bodies or attachment contents.
- Custom fields keep their stable Zammad API names. Sparse values become null, and nested values are serialized deterministically.