Skip to content

Ticketer Management

Ticketers connect your project to external ticket systems. The Weni CLI lets you create and configure generic ticketers from a YAML definition file, so agents and flows can open, forward, close, and reopen tickets through a partner HTTP service.

What are Ticketers?

A ticketer is the platform integration that routes ticket operations between VTEX CX and an external system. When a flow opens a ticket, the platform calls your partner service. When an agent replies or closes a ticket in the partner UI, the partner sends webhooks back to the platform.

The generic ticketer type uses a documented HTTP contract: any service that implements the required endpoints can be registered without custom Mailroom code.

Ticketer Types

Currently, the Weni CLI supports the following ticketer type:

  • generic: HTTP-based integration with a partner ticketer service (open, forward, close, reopen, and history endpoints).

Creating a Ticketer

To create a new ticketer, you need to:

  1. Create a ticketer definition file in YAML format
  2. Have a project selected (use weni project use <project_uuid>)
  3. Run the ticketer creation command

Command

weni ticketer create <ticketer_definition_file>

Arguments:

  • ticketer_definition_file: Path to the YAML file containing your ticketer configuration

Ticketer Definition Structure

A minimal ticketer definition (default HTTP contract, no custom templates):

ticketers:
  - name: "Generic Ticketer Integration"
    ticketer_type: "generic"
    config:
      base_url: "https://your-ticketer-host"
      api_token: "<API_TOKEN>"
      skip_webhook_hmac: "yes"
      project_name: "my project"
      route_open: "/v1/tickets"
      route_forward: "/v1/tickets/{external_id}/messages"
      route_close: "/v1/tickets/{external_id}/close"
      route_reopen: "/v1/tickets/{external_id}/reopen"
      route_history: "/v1/tickets/{external_id}/history"
      route_history_message: "/v1/tickets/{external_id}/messages"

Only the first item in the ticketers array is used per command execution.

Configuration Fields

Root Fields

Field Type Required Description
name string Yes Display name of the ticketer (max 100 characters)
ticketer_type string Yes Type of ticketer. Currently supports: generic
config object Yes Ticketer configuration object

Config Fields — credentials and metadata

Field Type Required Description
base_url string Yes Base URL of the partner ticketer service (must start with http:// or https://)
api_token string Yes Bearer token the platform uses when calling the partner service. Treat as a secret.
webhook_secret string Conditional Secret used to verify inbound webhooks from the partner. Required unless skip_webhook_hmac is enabled
skip_webhook_hmac string No Set to true, 1, or yes to skip webhook HMAC verification
project_uuid string No Project UUID sent in ticket metadata. Auto-filled from the selected project when empty or omitted
project_name string No Project name sent in ticket metadata

Config Fields — OAuth2 token refresh (optional)

Mailroom authenticates Platform → Ticketer with Authorization: Bearer <api_token>. Inbound webhooks stay HMAC (webhook_secret / skip_webhook_hmac); token refresh does not change that.

Ticketer config is a map[string]string. Nested objects cannot be stored as native JSON. token_refresh_config is accepted in the YAML as either a nested object or a JSON string; the CLI always writes a compact JSON string to the API/DB.

Leave these fields out to keep the current behavior (base_url + api_token + webhook only). There is no weni ticketer update command; create a new ticketer to change config.

Field Type Required Description
token_refresh_enabled string No Set to true, 1, or yes (case-insensitive) to enable refresh. Absent or any other value leaves refresh off.
token_refresh_type string If refresh is enabled custom (partner-defined body) or refresh (OAuth2 refresh_token grant). Authorization-code/redirect is out of scope.
token_refresh_config string If refresh is enabled Compact JSON string (see below). You may author it as a YAML object; the CLI serializes it.
refresh_token string If token_refresh_type=refresh Current refresh token. Treat as a secret. Mailroom updates it after a successful refresh when the response includes refresh_token_field.
expires_in string No Unix timestamp in seconds (decimal string) of when api_token expires. This is not the raw RFC expires_in duration.
client_id string No Used only for type=refresh (form body).
client_secret string No Used only for type=refresh (form body). Treat as a secret.

token_refresh_config JSON shape:

{
  "when": {
    "match": "any",
    "status_codes": [401, 403],
    "body_contains": ["INVALID_SESSION_ID", "token_expired"],
    "expired": true
  },
  "method": "POST",
  "url": "https://host/services/oauth2/token",
  "headers": {
    "Content-Type": "application/x-www-form-urlencoded"
  },
  "body": "grant_type=password&client_id=...&client_secret=...&username=...&password=...",
  "token_field": "access_token",
  "refresh_token_field": "refresh_token",
  "expires_in_field": "expires_in",
  "expires_in_default": 7200
}

Rules:

  • Refresh is enabled only when token_refresh_enabled is true, 1, or yes.
  • when.match, if present, must be any (OR). Empty/omitted defaults to any in Mailroom. all is not supported.
  • Refresh runs when a partner response status is in status_codes, or the body contains a body_contains substring, or (expired is true and now >= expires_in top-level).
  • method defaults to POST if omitted.
  • url is required.
  • token_field defaults to access_token and may be a dotted path (for example data.token).
  • body is required for custom and ignored for refresh (the CLI does not send it). Treat body as a secret.
  • For refresh, Mailroom sends application/x-www-form-urlencoded with grant_type=refresh_token&refresh_token=<config.refresh_token> and client_id/client_secret when those top-level fields exist. The CLI sets Content-Type to form-urlencoded when headers omit it.
  • expires_in_field is the RFC duration in seconds in the token response. Do not map Salesforce issued_at. If the partner omits a duration, set expires_in_default (for example 7200).
  • headers is a map of strings.
  • After a successful refresh, Mailroom updates api_token, expires_in (unix), and refresh_token (when present). The CLI only writes the initial state; it does not call the token URL.

Config Fields — routes

Field Type Required Description
route_open string No Override for the open-ticket endpoint. Default: /v1/tickets
route_forward string No Override for forwarding messages. Default: /v1/tickets/{external_id}/messages
route_close string No Override for closing tickets. Default: /v1/tickets/{external_id}/close
route_reopen string No Override for reopening tickets. Default: /v1/tickets/{external_id}/reopen
route_history string No Override for batch history. Default: /v1/tickets/{external_id}/history
route_history_message string No Override for one-by-one history. Default: same as route_forward (/v1/tickets/{external_id}/messages)

Config Fields — conversation history

Field Type Required Description
history_mode string No How history is sent after open: batch (default) or one_by_one
history_batch_size string No Max messages per batch request when history_mode=batch. Default: 50
history_mode Endpoint used Payload shape
batch route_history HistoryRequest with a messages array
one_by_one route_history_message / route_forward One MessageRequest per message (same as forward)

Config Fields — payload templates

Optional Go text/template strings that customize request or response bodies. When a template is absent or empty, the platform uses the default HTTP contract.

Helpers available in templates: json (serialize a value as JSON) and toString.

Prefer {{json .field}} for strings and nested objects so quotes, newlines, and null values stay valid JSON. If you expand objects field-by-field without json, keep metadata.webhook_base_url in open_template — the partner needs it to send agent replies back to the platform.

Field Direction Description
open_template Platform → Ticketer Replaces the default open-ticket request body
open_response_template Platform ← Ticketer Maps the partner open response to {external_id, status, created_at}
forward_template Platform → Ticketer Replaces the default forward-message request body
forward_response_template Platform ← Ticketer Maps the partner forward response to {message_external_id, status}
close_template Platform → Ticketer Replaces the default close-ticket request body
close_response_template Platform ← Ticketer Maps the partner close response to {status}
history_template Platform → Ticketer Replaces the history request body (batch or one_by_one context)
history_response_template Platform ← Ticketer Maps the partner history response to {status, messages_received}
messages_template Ticketer → Platform Maps the inbound agent-message webhook body to the standard envelope
messages_response_template Ticketer ← Platform Replaces the success response returned to the partner for agent messages
tickets_close_template Ticketer → Platform Maps the inbound close webhook body to the standard envelope
tickets_close_response_template Ticketer ← Platform Replaces the success response returned to the partner for close webhooks

All config values sent to the API must be strings. token_refresh_config is the only field you may author as a YAML object; it is serialized to a compact JSON string before create.

Integration Flow

1. Platform opens ticket          → POST {base_url}{route_open}
2. Partner returns                → 201 { external_id }
3. Platform sends history         → batch: POST {base_url}{route_history}
                                    one_by_one: POST {base_url}{route_history_message} (per message)
4. Platform forwards new message  → POST {base_url}{route_forward}
5. Agent replies in partner       → POST {webhook_base_url}/messages
6. Agent closes in partner        → POST {webhook_base_url}/tickets/close

On ticket open, the platform includes metadata.webhook_base_url so the partner knows where to send inbound events.

Example: Creating a Generic Ticketer

Step 1: Prepare the partner service

Run your ticketer service and copy the credentials it exposes (for example, from server logs or a settings screen):

  • API_TOKEN — used in config.api_token
  • WEBHOOK_SECRET — used in config.webhook_secret

For local development, expose the service with a public URL (for example, ngrok) and use that URL as base_url.

Step 2: Create the ticketer definition file

Minimal (default payloads and routes):

ticketers:
  - name: "Generic Ticketer Integration"
    ticketer_type: "generic"
    config:
      base_url: "https://your-ticketer-host"
      api_token: "your-api-token"
      skip_webhook_hmac: "yes"
      project_name: "my org"

With OAuth2 token refresh (custom, Salesforce-style password grant):

ticketers:
  - name: "Salesforce Ticketer"
    ticketer_type: "generic"
    config:
      base_url: "https://your-ticketer-host"
      api_token: "<current access token>"
      webhook_secret: "<webhook secret>"
      token_refresh_enabled: "true"
      token_refresh_type: "custom"
      expires_in: "1774132800"
      token_refresh_config:
        when:
          match: "any"
          status_codes: [401, 403]
          body_contains: ["INVALID_SESSION_ID", "token_expired"]
          expired: true
        method: "POST"
        url: "https://....my.salesforce.com/services/oauth2/token"
        headers:
          Content-Type: "application/x-www-form-urlencoded"
        body: "grant_type=password&client_id=...&client_secret=...&username=...&password=..."
        token_field: "access_token"
        expires_in_field: "expires_in"
        expires_in_default: 7200

With OAuth2 token refresh (refresh grant): do not set body; Mailroom builds the form from refresh_token and optional client_id / client_secret.

ticketers:
  - name: "Partner Ticketer (refresh grant)"
    ticketer_type: "generic"
    config:
      base_url: "https://your-ticketer-host"
      api_token: "<current access token>"
      webhook_secret: "<webhook secret>"
      token_refresh_enabled: "true"
      token_refresh_type: "refresh"
      refresh_token: "<refresh token>"
      client_id: "<optional client id>"
      client_secret: "<optional client secret>"
      token_refresh_config:
        when:
          match: "any"
          status_codes: [401]
          expired: true
        url: "https://partner.example.com/oauth/token"
        token_field: "access_token"
        refresh_token_field: "refresh_token"
        expires_in_field: "expires_in"
        expires_in_default: 7200

With identity templates (custom templates that recreate the default contract). Useful to validate template wiring without changing payload shape:

ticketers:
  - name: "Generic Ticketer (equivalent templates)"
    ticketer_type: "generic"
    config:
      base_url: "https://your-ticketer-host"
      api_token: "your-api-token"
      skip_webhook_hmac: "yes"
      history_mode: "batch"
      history_batch_size: "50"
      open_template: |-
        {"ticket_id":{{json .ticket_id}},"topic":{{json .topic}},"contact":{{json .contact}},"body":{{json .body}},"assignee":{{json .assignee}},"metadata":{{json .metadata}},"opened_at":{{json .opened_at}}}
      open_response_template: |-
        {"external_id":{{json .external_id}},"status":{{json .status}},"created_at":{{json .created_at}}}
      forward_template: |-
        {"ticket_id":{{json .ticket_id}},"external_id":{{json .external_id}},"message_id":{{json .message_id}},"direction":{{json .direction}},"sender":{{json .sender}},"text":{{json .text}},"attachments":{{json .attachments}},"metadata":{{json .metadata}},"sent_at":{{json .sent_at}}}
      # … remaining templates (close, history, messages, tickets_close) follow the same pattern

History one-by-one (each history message posted to the forward endpoint):

ticketers:
  - name: "Generic Ticketer (history one-by-one)"
    ticketer_type: "generic"
    config:
      base_url: "https://your-ticketer-host"
      api_token: "your-api-token"
      skip_webhook_hmac: "yes"
      history_mode: "one_by_one"
      route_history_message: "/v1/tickets/{external_id}/messages"
      # Same shape as forward_template (MessageRequest per message)
      history_template: |-
        {"ticket_id":{{json .ticket_id}},"external_id":{{json .external_id}},"message_id":{{json .message_id}},"direction":{{json .direction}},"sender":{{json .sender}},"text":{{json .text}},"attachments":{{json .attachments}},"metadata":{{json .metadata}},"sent_at":{{json .sent_at}}}
      # Partner usually replies with a forward-style body; map it to the history envelope
      history_response_template: |-
        {"status":"history_received","messages_received":1}

Step 3: Ensure you have a project selected

weni project list
weni project use <your_project_uuid>

Step 4: Create the ticketer

weni ticketer create my_ticketer.yaml

On success, the CLI displays the ticketer name and UUID.

Best Practices

  1. Match routes to your partner API: If you override route_* fields, ensure they match the endpoints your service actually exposes
  2. Secure your credentials: Never commit ticketer definition files with real tokens to version control
  3. Use environment-specific files: Maintain separate definitions for development, staging, and production
  4. Align HMAC settings: If the partner runs with SKIP_WEBHOOK_HMAC=true, set skip_webhook_hmac: "true" in the ticketer config as well
  5. Preserve webhook_base_url in custom open templates: When using open_template, include the full metadata object (for example with {{json .metadata}}) so agent replies can reach the platform
  6. Prefer {{json …}} in templates: Avoid breaking JSON when values contain quotes, newlines, or are null
  7. Test the HTTP contract: Validate open, history, forward, close, reopen, and webhook flows before using the ticketer in production flows
  8. Keep token-refresh secrets out of logs and git: api_token, client_secret, refresh_token, and token_refresh_config.body are secrets
  9. Use unix expires_in: If you set top-level expires_in, it must be a decimal unix timestamp of token expiry, not the RFC duration from the token response

Common Use Cases

  • Custom support desks: Connect flows to an in-house ticketing UI
  • Partner integrations: Plug in any HTTP service that implements the generic ticketer contract
  • Local development: Register a local or tunneled reference service for end-to-end testing
  • Payload adapters: Use templates when the partner API shape differs from the default contract without writing Mailroom code

Troubleshooting

"Ticketer definition path is required"

Provide the path to your YAML file as an argument to the command.

"No project selected, please select a project first"

Select a project first using weni project use <project_uuid>.

"'config.webhook_secret' is required unless 'config.skip_webhook_hmac' is set"

Add webhook_secret to your config, or set skip_webhook_hmac: "true|yes|1" when HMAC verification is disabled on both sides.

"'config.\<field>' is not a recognized field"

Only fields listed in this guide are accepted. Check spelling of template and route keys.

"'config.token_refresh_type' is required when token refresh is enabled"

Set token_refresh_type to custom or refresh when token_refresh_enabled is true, 1, or yes. Omit refresh fields entirely if you do not need token refresh.

"'config.token_refresh_config' must be valid JSON"

token_refresh_config must parse as a JSON object (or a YAML object that the CLI serializes). It is stored as a compact JSON string, not as nested JSON in tickets_ticketer.config.

URL validation errors

Ensure base_url starts with http:// or https://.

Agent replies do not reach the contact

Confirm the partner is calling the URL from metadata.webhook_base_url on open. If you customize open_template, do not drop that field from metadata.

API errors after creation

The CLI calls POST api/v1/ticketers on the Weni CLI backend. If creation fails, confirm the backend endpoint is available and that your account has permission for the selected project.

Next Steps

After creating a ticketer:

  1. Use the returned UUID in flows or ticket actions that reference a ticketer
  2. Open a test ticket and confirm the partner receives the open request
  3. Confirm history delivery (batch or one_by_one) matches your history_mode
  4. Verify agent replies and close events reach the platform through webhooks
  5. Monitor ticket operations in the VTEX CX Platform dashboard

For the full HTTP contract your partner service must implement (endpoints, payloads, HMAC, and template context), see the generic ticketer service documentation in your integration repository (generic-ticketer-service.md).