FS-DOC-05Technical
Integration and API Spec
Complaint Escalation Workflow
[YourCompany.com] · Customer Support Department · Prepared by FullSpec · [Today's Date]
This document is the definitive integration reference for the Complaint Escalation Workflow automation. It covers every tool connected to the orchestration layer, the exact credentials and scopes required, field mappings across agent handoffs, orchestration structure, and the error handling behaviour required at each integration point. The FullSpec team uses this spec to build, configure, and validate all connections. No credentials should be hardcoded in workflow logic; all secrets live in the shared credential store as described in Section 04.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agents
Orchestration layer
Workflow automation tool coordinating all agents and step sequencing end to end
Internal service account plus per-tool credential store entries
Any plan supporting webhook triggers and credential vaults
All agents
Zendesk
Ticket creation, field updates, assignment, priority changes, and status tracking
OAuth 2.0 / API token
Suite Team or higher (API access required)
Agent 1, Agent 2
HubSpot
Customer record lookup: purchase history, lifetime value, prior complaints
OAuth 2.0 (private app token)
Free CRM or higher (Contacts API included)
Agent 1
Slack
Agent notification, manager high-severity alert, and overdue escalation alert
OAuth 2.0 (bot token via Slack App)
Free or higher (incoming webhooks available on all plans)
Agent 2
Gmail
Outbound customer acknowledgement email personalised by severity tier
OAuth 2.0 (Google Workspace service account or user delegation)
Google Workspace Business Starter or personal Gmail with OAuth
Agent 2
Notion
Resolution log and knowledge base append on ticket close
Notion Integration Token (internal integration)
Free or higher (API access on all plans)
Agent 2
Before you connect anything: provision sandbox or test instances for Zendesk, HubSpot, Slack, and Gmail before touching production credentials. Validate every OAuth flow, webhook delivery, and field read/write against test data first. Only after all connections are confirmed working in the sandbox environment should production credentials be entered into the credential store.
02Per-tool integration detail
Zendesk
Zendesk is the primary ticketing system. It receives the initial complaint, stores triage output as ticket fields, manages assignment, tracks SLA deadlines, and records priority escalations. Both agents read from and write to Zendesk.
Auth method
OAuth 2.0. Generate an API token under Admin > Apps and Integrations > Zendesk API. Store as ZENDESK_API_TOKEN in the credential store. Subdomain stored as ZENDESK_SUBDOMAIN.
Required scopes
tickets:read, tickets:write, users:read, users:write, organizations:read
Webhook / trigger setup
Create a Zendesk webhook under Admin > Objects and Rules > Webhooks pointing to the orchestration layer inbound URL. Create a Trigger (Admin > Objects and Rules > Triggers) firing on 'Ticket is created' with condition 'Channel is Email or Web form'. The trigger calls the webhook and passes ticket.id, ticket.subject, ticket.description, ticket.requester.email, ticket.created_at.
Required configuration
Custom ticket fields must be created before build: 'cf_severity_score' (dropdown: low/medium/high), 'cf_complaint_category' (text), 'cf_hubspot_contact_id' (text), 'cf_hubspot_lifetime_value' (decimal). Field IDs stored in credential store as ZENDESK_CF_SEVERITY_ID, ZENDESK_CF_CATEGORY_ID, ZENDESK_CF_HS_CONTACT_ID, ZENDESK_CF_HS_LTV_ID. Routing groups (e.g. tier-1-support, escalations) must be pre-configured; group IDs stored as ZENDESK_GROUP_LOW_ID, ZENDESK_GROUP_MED_ID, ZENDESK_GROUP_HIGH_ID.
Rate limits
700 requests/minute on Suite Team. At ~90 complaints/month (~3/day peak) the workflow generates approximately 15-20 API calls per complaint across both agents. Peak load is well under 1% of the rate limit. No throttling required at current volume.
Constraints
Webhook signature validation: Zendesk signs webhook payloads with HMAC-SHA256. The orchestration layer must verify the X-Zendesk-Webhook-Signature header against ZENDESK_WEBHOOK_SECRET before processing any payload. Ticket field IDs are environment-specific and must not be hardcoded; always reference credential store variables.
// Inbound payload (Zendesk webhook on ticket creation)
{
"ticket_id": "<integer>",
"subject": "<string>",
"description": "<string>",
"requester_email": "<string>",
"requester_name": "<string>",
"channel": "email | web_form",
"created_at": "<ISO8601 timestamp>"
}
// Outbound (PATCH /api/v2/tickets/{ticket_id}.json)
{
"ticket": {
"custom_fields": [
{ "id": "{{ZENDESK_CF_SEVERITY_ID}}", "value": "low|medium|high" },
{ "id": "{{ZENDESK_CF_CATEGORY_ID}}", "value": "<category string>" },
{ "id": "{{ZENDESK_CF_HS_CONTACT_ID}}", "value": "<hs_contact_id>" },
{ "id": "{{ZENDESK_CF_HS_LTV_ID}}", "value": "<lifetime_value>" }
],
"group_id": "{{ZENDESK_GROUP_<SEVERITY>_ID}}",
"assignee_id": "<agent_user_id>",
"priority": "low|normal|high|urgent"
}
}HubSpot
HubSpot supplies customer context to the Complaint Triage Agent. The automation performs a contact lookup by email and retrieves purchase history, lifetime value, and prior support interaction count. Results are written back to the Zendesk ticket as custom field values.
Auth method
Private app token (OAuth 2.0 scoped). Create a private app under Settings > Integrations > Private Apps. Store the token as HUBSPOT_PRIVATE_APP_TOKEN in the credential store. No client ID or secret needed for private app flow.
Required scopes
crm.objects.contacts.read, crm.objects.contacts.write (optional for tagging), crm.objects.deals.read, tickets.read
Webhook / trigger setup
No inbound webhook from HubSpot is required. The Triage Agent calls HubSpot on demand via GET after receiving the Zendesk ticket payload. No HubSpot subscription or workflow trigger is configured for this automation.
Required configuration
HubSpot custom contact properties needed: 'prior_complaint_count' (number), 'customer_tier' (enumeration: standard/premium/enterprise). Property internal names stored as HUBSPOT_PROP_COMPLAINT_COUNT and HUBSPOT_PROP_CUSTOMER_TIER. The base API URL is https://api.hubapi.com and must be stored as HUBSPOT_API_BASE_URL. Do not hardcode.
Rate limits
Private apps receive 100 requests/10 seconds (burst) and 40,000 requests/day. At 90 complaints/month, the automation issues 2-3 calls per complaint (contact search, contact get, deals list). Daily volume is approximately 10 calls on busy days, well under limits. No throttling required.
Constraints
Contact lookup is by email (GET /crm/v3/objects/contacts/search with filterGroups on email). If no contact is found, the agent must continue with a null context object and flag the ticket with cf_hubspot_contact_id = 'not_found'. The workflow must not halt on a missing HubSpot record.
// Outbound request (contact search by email)
POST https://api.hubapi.com/crm/v3/objects/contacts/search
Authorization: Bearer {{HUBSPOT_PRIVATE_APP_TOKEN}}
{
"filterGroups": [{
"filters": [{ "propertyName": "email", "operator": "EQ", "value": "{{requester_email}}" }]
}],
"properties": ["firstname", "lastname", "email", "lifecyclestage",
"prior_complaint_count", "customer_tier", "hs_object_id",
"total_revenue"]
}
// Inbound response (trimmed)
{
"results": [{
"id": "<hs_contact_id>",
"properties": {
"firstname": "<string>",
"lastname": "<string>",
"email": "<string>",
"customer_tier": "standard|premium|enterprise",
"prior_complaint_count": "<integer string>",
"total_revenue": "<decimal string>"
}
}]
}Slack
Slack carries three distinct notification types: an agent assignment alert (all severities), a manager high-severity alert (high severity only), and an overdue escalation alert (triggered when a ticket breaches its SLA deadline). Each notification type posts to a dedicated channel.
Auth method
Slack Bot Token (OAuth 2.0). Create a Slack App at api.slack.com/apps, add the bot to the workspace, and install it. Store the bot token as SLACK_BOT_TOKEN. Store each channel ID (not name) as SLACK_CHANNEL_AGENT_ALERTS, SLACK_CHANNEL_MANAGER_ALERTS, SLACK_CHANNEL_ESCALATIONS in the credential store.
Required scopes
chat:write, chat:write.public, channels:read, users:read, users:read.email
Webhook / trigger setup
No inbound Slack webhook is used. All messages are sent outbound from the orchestration layer via POST to https://slack.com/api/chat.postMessage. For the overdue alert, the orchestration layer runs a scheduled poll every 15 minutes checking for tickets where (current_time minus ticket_created_at) exceeds the severity SLA threshold. SLA thresholds stored as SLACK_SLA_LOW_MINS, SLACK_SLA_MED_MINS, SLACK_SLA_HIGH_MINS.
Required configuration
Three Slack channels must be created and the bot invited before build: #support-agent-alerts, #support-manager-escalations, #support-overdue. Channel IDs (not names) stored as above. Message templates stored as SLACK_MSG_AGENT_TEMPLATE, SLACK_MSG_MANAGER_TEMPLATE, SLACK_MSG_OVERDUE_TEMPLATE using Block Kit JSON. Do not hardcode channel names or message text.
Rate limits
Tier 3 methods (chat.postMessage): 1 request/second. At peak volume of 3 complaints/day producing up to 3 Slack messages each, the maximum send rate is approximately 9 messages/day. No throttling required. A 1-second delay between sequential Slack calls should be enforced as a precaution.
Constraints
Messages must include a direct link to the Zendesk ticket (https://{{ZENDESK_SUBDOMAIN}}.zendesk.com/agent/tickets/{{ticket_id}}). User lookups to map agent email to Slack user ID must use users.lookupByEmail and cache results per session to avoid redundant API calls.
// Outbound (agent assignment alert)
POST https://slack.com/api/chat.postMessage
Authorization: Bearer {{SLACK_BOT_TOKEN}}
{
"channel": "{{SLACK_CHANNEL_AGENT_ALERTS}}",
"blocks": [
{ "type": "section", "text": { "type": "mrkdwn",
"text": "*New complaint assigned* | Severity: {{severity}} | <{{ticket_url}}|View ticket>" }
}
]
}
// Outbound (overdue escalation alert)
{
"channel": "{{SLACK_CHANNEL_ESCALATIONS}}",
"blocks": [
{ "type": "section", "text": { "type": "mrkdwn",
"text": ":rotating_light: *Overdue complaint* | Ticket #{{ticket_id}} | Assigned: {{assignee_name}} | <{{ticket_url}}|View ticket>" }
}
]
}Integration and API SpecPage 1 of 3
FS-DOC-05Technical
Gmail
Gmail sends the outbound customer acknowledgement email. The email is personalised with the customer's name, the assigned agent's name, and the expected response time based on the severity tier. A separate template is maintained for each of the three severity levels.
Auth method
OAuth 2.0 via Google Workspace. Use a service account with domain-wide delegation, or a dedicated support mailbox with user-level OAuth consent. Store refresh token as GMAIL_OAUTH_REFRESH_TOKEN, client ID as GMAIL_CLIENT_ID, client secret as GMAIL_CLIENT_SECRET, and the sending address as GMAIL_FROM_ADDRESS.
Required scopes
https://www.googleapis.com/auth/gmail.send
Webhook / trigger setup
No inbound Gmail trigger is used in this direction. Gmail is called outbound by the Escalation and Routing Agent after severity scoring is complete. If inbound complaint ingestion via Gmail is required (in addition to Zendesk webhook), a Gmail push notification via Google Pub/Sub can be configured; this is a separate intake connector scoped to https://www.googleapis.com/auth/gmail.readonly and https://www.googleapis.com/auth/gmail.modify.
Required configuration
Three email templates must be drafted and stored as GMAIL_TEMPLATE_LOW, GMAIL_TEMPLATE_MED, GMAIL_TEMPLATE_HIGH in the credential store (or a linked Notion page ID). Each template contains placeholders: {{customer_first_name}}, {{ticket_id}}, {{assigned_agent_name}}, {{expected_response_time}}. The sending address must have a configured signature in Google Workspace.
Rate limits
Gmail API: 250 quota units/second per user; sending limit 500 messages/day per user on Workspace. At 90 complaints/month (~3/day) this is far below limits. No throttling required.
Constraints
Access token expires after 3600 seconds. The orchestration layer must use the refresh token flow automatically before each call. Never store the access token as a long-lived credential. The From address must match the authorised OAuth identity or Google will reject the send request.
// Outbound (send acknowledgement via Gmail API)
POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
Authorization: Bearer {{access_token_refreshed}}
{
"raw": "<base64url-encoded RFC 2822 message>",
// Decoded headers:
// From: {{GMAIL_FROM_ADDRESS}}
// To: {{requester_email}}
// Subject: Re: Your complaint has been received [Ticket #{{ticket_id}}]
// Content-Type: text/html; charset=UTF-8
// Body: rendered from GMAIL_TEMPLATE_{{SEVERITY}}
// with {{customer_first_name}}, {{ticket_id}},
// {{assigned_agent_name}}, {{expected_response_time}} substituted
}Notion
Notion stores the resolution log and knowledge base. When a Zendesk ticket is closed, the Escalation and Routing Agent appends a structured row to the resolution log database in Notion. This replaces the manual summary sometimes added to a shared page.
Auth method
Notion Internal Integration Token. Create an integration at notion.so/my-integrations, share the target database with the integration, and store the token as NOTION_INTEGRATION_TOKEN. The resolution log database ID is stored as NOTION_RESOLUTION_DB_ID.
Required scopes
Internal integration capabilities: Insert content, Read content. Granted at the database level when the integration is added to the Notion page.
Webhook / trigger setup
No inbound webhook from Notion. The orchestration layer writes to Notion outbound when a Zendesk ticket status changes to 'solved'. This can be captured via a second Zendesk webhook trigger firing on 'Ticket status changed to solved'.
Required configuration
The Notion resolution log database must contain the following properties before build: 'Ticket ID' (title), 'Customer Email' (email), 'Severity' (select: low/medium/high), 'Category' (text), 'Resolution Summary' (text), 'Resolved At' (date), 'Assigned Agent' (text). The database must be shared with the integration. Database ID stored as NOTION_RESOLUTION_DB_ID. Do not hardcode.
Rate limits
Notion API: 3 requests/second average. At 90 complaints/month resolved, the automation sends approximately 3 page-create calls/day. No throttling required.
Constraints
Notion API returns 400 if a required property type does not match the expected schema. The orchestration layer must validate all field types before posting. If the Notion write fails, the failure must be logged and an alert sent to SLACK_CHANNEL_ESCALATIONS; the Zendesk ticket close must not be blocked by a Notion write failure.
// Outbound (create resolution log page)
POST https://api.notion.com/v1/pages
Authorization: Bearer {{NOTION_INTEGRATION_TOKEN}}
Notion-Version: 2022-06-28
{
"parent": { "database_id": "{{NOTION_RESOLUTION_DB_ID}}" },
"properties": {
"Ticket ID": { "title": [{ "text": { "content": "{{ticket_id}}" } }] },
"Customer Email": { "email": "{{requester_email}}" },
"Severity": { "select": { "name": "{{severity}}" } },
"Category": { "rich_text": [{ "text": { "content": "{{complaint_category}}" } }] },
"Resolution Summary":{ "rich_text": [{ "text": { "content": "{{resolution_notes}}" } }] },
"Resolved At": { "date": { "start": "{{resolved_at_iso8601}}" } },
"Assigned Agent": { "rich_text": [{ "text": { "content": "{{assigned_agent_name}}" } }] }
}
}03Field mappings between tools
The tables below document every field handoff between tools, ordered by agent. Monospace field names match the exact property keys used in API requests and responses. All custom Zendesk field references use the credential store variable name, not a hardcoded numeric ID.
Handoff A: Inbound complaint to Zendesk ticket (pre-triage, triggered by webhook)
Source tool
Source field
Destination tool
Destination field
Email / web form
`sender_email`
Zendesk
`requester.email`
Email / web form
`sender_name`
Zendesk
`requester.name`
Email / web form
`subject`
Zendesk
`ticket.subject`
Email / web form
`body`
Zendesk
`ticket.description`
Email / web form
`attachments[]`
Zendesk
`ticket.uploads[]`
Email / web form
`received_at`
Zendesk
`ticket.created_at` (auto)
Handoff B: Zendesk ticket to Complaint Triage Agent (Agent 1 reads and enriches)
Source tool
Source field
Destination tool
Destination field
Zendesk
`ticket.id`
HubSpot (lookup key)
`filterGroups[].filters[].value` (email)
Zendesk
`ticket.description`
Triage logic
`complaint_text` (NLP input)
Zendesk
`requester.email`
HubSpot
`properties.email` (search filter)
HubSpot
`properties.hs_object_id`
Zendesk
`cf_hubspot_contact_id` via `{{ZENDESK_CF_HS_CONTACT_ID}}`
HubSpot
`properties.total_revenue`
Zendesk
`cf_hubspot_lifetime_value` via `{{ZENDESK_CF_HS_LTV_ID}}`
HubSpot
`properties.customer_tier`
Triage logic
`customer_tier` (severity weight input)
HubSpot
`properties.prior_complaint_count`
Triage logic
`prior_complaint_count` (severity weight input)
Triage logic output
`severity_score`
Zendesk
`cf_severity_score` via `{{ZENDESK_CF_SEVERITY_ID}}`
Triage logic output
`complaint_category`
Zendesk
`cf_complaint_category` via `{{ZENDESK_CF_CATEGORY_ID}}`
Handoff C: Triage output to Escalation and Routing Agent (Agent 2 acts on severity)
Source tool
Source field
Destination tool
Destination field
Zendesk
`cf_severity_score`
Zendesk (update)
`ticket.priority` (low=low, medium=normal, high=urgent)
Zendesk
`cf_severity_score`
Zendesk (update)
`ticket.group_id` via `{{ZENDESK_GROUP_<SEVERITY>_ID}}`
Zendesk
`ticket.id`
Slack message
`ticket_url` constructed as `https://{{ZENDESK_SUBDOMAIN}}.zendesk.com/agent/tickets/{{ticket_id}}`
Zendesk
`ticket.subject`
Slack message
`text` block summary
Zendesk
`cf_severity_score`
Slack channel routing
`channel` selection (agent vs manager channel)
Zendesk
`requester.name`
Gmail template
`{{customer_first_name}}` (first token of name)
Zendesk
`ticket.id`
Gmail template
`{{ticket_id}}`
Zendesk
`ticket.assignee.name`
Gmail template
`{{assigned_agent_name}}`
Zendesk
`cf_severity_score`
Gmail template selection
`GMAIL_TEMPLATE_{{SEVERITY}}` key lookup
HubSpot
`properties.total_revenue`
Slack manager alert
`customer_lifetime_value` field in Block Kit message
Handoff D: Ticket resolution to Notion log (on Zendesk ticket status = solved)
Source tool
Source field
Destination tool
Destination field
Zendesk
`ticket.id`
Notion
`Ticket ID` (title property)
Zendesk
`requester.email`
Notion
`Customer Email` (email property)
Zendesk
`cf_severity_score`
Notion
`Severity` (select property)
Zendesk
`cf_complaint_category`
Notion
`Category` (rich_text property)
Zendesk
`ticket.comment` (resolution note)
Notion
`Resolution Summary` (rich_text property)
Zendesk
`ticket.updated_at`
Notion
`Resolved At` (date property)
Zendesk
`ticket.assignee.name`
Notion
`Assigned Agent` (rich_text property)
Integration and API SpecPage 2 of 3
FS-DOC-05Technical
04Build stack and orchestration
Orchestration layout
Two discrete workflows, one per agent. Workflow 1 handles the Complaint Triage Agent (Zendesk webhook ingest, HubSpot lookup, severity scoring, Zendesk field update). Workflow 2 handles the Escalation and Routing Agent (ticket assignment in Zendesk, Slack notifications, Gmail acknowledgement, overdue SLA poll and alert, Notion resolution log). Both workflows share a single credential store. No logic is shared between workflows at runtime; Workflow 2 is triggered by a state change on the Zendesk ticket written by Workflow 1.
Agent 1 trigger mechanism
Webhook (push). Zendesk fires a webhook to the orchestration layer inbound URL the moment a ticket is created. Payload signature validated via HMAC-SHA256 using ZENDESK_WEBHOOK_SECRET before any processing begins. Duplicate ticket IDs within a 60-second window are deduplicated using an in-memory idempotency key.
Agent 2 trigger mechanism
Webhook (push) for assignment, Gmail, and Slack steps: fired by a second Zendesk trigger on 'Ticket custom field cf_severity_score is present'. Scheduled poll (every 15 minutes) for overdue SLA check: the orchestration layer queries Zendesk for open tickets where time since creation exceeds SLA_<SEVERITY>_MINS and no solved status is set. Webhook signature validated using the same ZENDESK_WEBHOOK_SECRET. Poll uses the Zendesk Search API with a time-range filter. Notion write triggered by a third Zendesk webhook on 'Ticket status changed to solved'.
Credential store format
All secrets and environment-specific IDs are stored in the orchestration platform's encrypted credential store. No secret or ID appears as a literal value in any workflow node. See code block below for full contents.
Shared credential store
One store, referenced by both Workflow 1 and Workflow 2 using the variable names listed in the code block below.
Credential store contents (all entries required before build starts)
# Zendesk
ZENDESK_SUBDOMAIN = <your-subdomain> # e.g. acme (not acme.zendesk.com)
ZENDESK_API_TOKEN = <api_token_string>
ZENDESK_WEBHOOK_SECRET = <hmac_secret_string> # from Zendesk webhook config
ZENDESK_CF_SEVERITY_ID = <integer> # custom field ID for cf_severity_score
ZENDESK_CF_CATEGORY_ID = <integer> # custom field ID for cf_complaint_category
ZENDESK_CF_HS_CONTACT_ID = <integer> # custom field ID for HubSpot contact ID
ZENDESK_CF_HS_LTV_ID = <integer> # custom field ID for HubSpot lifetime value
ZENDESK_GROUP_LOW_ID = <integer> # group ID for low-severity routing
ZENDESK_GROUP_MED_ID = <integer> # group ID for medium-severity routing
ZENDESK_GROUP_HIGH_ID = <integer> # group ID for high-severity routing
# HubSpot
HUBSPOT_PRIVATE_APP_TOKEN = pat-<token_string>
HUBSPOT_API_BASE_URL = https://api.hubapi.com
HUBSPOT_PROP_COMPLAINT_COUNT = prior_complaint_count # internal property name
HUBSPOT_PROP_CUSTOMER_TIER = customer_tier # internal property name
# Slack
SLACK_BOT_TOKEN = xoxb-<token_string>
SLACK_CHANNEL_AGENT_ALERTS = <channel_id> # C0XXXXXXXXX format
SLACK_CHANNEL_MANAGER_ALERTS = <channel_id>
SLACK_CHANNEL_ESCALATIONS = <channel_id>
SLACK_SLA_LOW_MINS = 480 # 8 hours
SLACK_SLA_MED_MINS = 240 # 4 hours
SLACK_SLA_HIGH_MINS = 60 # 1 hour
SLACK_MSG_AGENT_TEMPLATE = <block_kit_json_string>
SLACK_MSG_MANAGER_TEMPLATE = <block_kit_json_string>
SLACK_MSG_OVERDUE_TEMPLATE = <block_kit_json_string>
# Gmail
GMAIL_CLIENT_ID = <google_oauth_client_id>
GMAIL_CLIENT_SECRET = <google_oauth_client_secret>
GMAIL_OAUTH_REFRESH_TOKEN = <refresh_token_string>
GMAIL_FROM_ADDRESS = support@[YourCompany.com]
GMAIL_TEMPLATE_LOW = <html_template_string_or_notion_page_id>
GMAIL_TEMPLATE_MED = <html_template_string_or_notion_page_id>
GMAIL_TEMPLATE_HIGH = <html_template_string_or_notion_page_id>
# Notion
NOTION_INTEGRATION_TOKEN = secret_<token_string>
NOTION_RESOLUTION_DB_ID = <32_char_database_id>
Never commit credential store values to version control. If the orchestration platform exports workflow definitions as JSON or YAML, confirm that credential references export as variable names only and that no resolved secret values appear in the export. Rotate ZENDESK_WEBHOOK_SECRET and HUBSPOT_PRIVATE_APP_TOKEN immediately if either is exposed.
05Error handling and retry logic
Every integration point in the workflow has a defined failure behaviour. Unhandled exceptions must never fail silently. Where retries are specified, use exponential backoff starting at the stated base interval. After all retries are exhausted, the fallback action must execute and an alert must be posted to SLACK_CHANNEL_ESCALATIONS.
Integration
Scenario
Required behaviour
Zendesk webhook ingest
Signature validation fails (invalid or missing X-Zendesk-Webhook-Signature)
Reject payload immediately with HTTP 401. Log the raw headers and payload hash. Post alert to SLACK_CHANNEL_ESCALATIONS. Do not process the ticket. No retry.
Zendesk webhook ingest
Duplicate ticket ID received within 60-second window
Drop the duplicate silently using the idempotency key cache. Log the duplicate ticket ID at INFO level. No alert required.
Zendesk ticket create / update (PATCH)
HTTP 429 rate limit response
Retry after the Retry-After header value. Maximum 3 retries with exponential backoff (base 5 seconds, max 60 seconds). If all retries fail, log the ticket ID and post an alert to SLACK_CHANNEL_ESCALATIONS with the ticket ID for manual field update.
Zendesk ticket create / update (PATCH)
HTTP 5xx server error
Retry up to 3 times with exponential backoff (base 10 seconds, max 120 seconds). On final failure, post alert to SLACK_CHANNEL_ESCALATIONS and log the full request payload for manual resubmission.
HubSpot contact search
No contact found for requester email
Continue workflow with a null HubSpot context object. Set cf_hubspot_contact_id to 'not_found'. Severity scoring falls back to keyword and sentiment analysis only (no customer tier weighting). Do not halt or alert.
HubSpot contact search
HTTP 401 Unauthorized (token expired or revoked)
Log the error and post an alert to SLACK_CHANNEL_ESCALATIONS immediately. Do not retry with the same token. Pause the Triage Agent workflow until HUBSPOT_PRIVATE_APP_TOKEN is rotated and re-stored. Fallback: continue with null HubSpot context so the Zendesk ticket is still created and routed.
HubSpot contact search
HTTP 429 rate limit
Retry after the Retry-After header value (typically 10 seconds). Maximum 3 retries. On final failure, continue with null HubSpot context and log the failure against the ticket ID.
Slack message send (chat.postMessage)
HTTP 429 rate limit (ratelimited response body)
Wait 1 second (enforced minimum between calls). Retry up to 3 times. If all retries fail, log the intended message payload and post to SLACK_CHANNEL_ESCALATIONS via a secondary webhook URL stored as SLACK_FALLBACK_WEBHOOK_URL.
Slack message send (chat.postMessage)
channel_not_found or not_in_channel error
Halt the Slack send step. Log the error and the channel ID that failed. Post an alert to the fallback webhook (SLACK_FALLBACK_WEBHOOK_URL). Do not continue to Gmail step until Slack delivery is confirmed or manually bypassed.
Gmail send
OAuth access token expired
Refresh the access token automatically using GMAIL_OAUTH_REFRESH_TOKEN before each send request. If the refresh call fails (invalid_grant), log the error, skip the Gmail send, post an alert to SLACK_CHANNEL_ESCALATIONS, and flag the Zendesk ticket with a tag 'ack-email-failed' for manual follow-up.
Gmail send
HTTP 400 invalid recipient address
Log the invalid address and the ticket ID. Skip the send. Apply the 'ack-email-failed' tag to the Zendesk ticket and post an alert to SLACK_CHANNEL_AGENT_ALERTS with the ticket ID so the assigned agent can send the acknowledgement manually.
Notion page create (resolution log)
HTTP 400 schema mismatch or missing required property
Log the full request payload and the Notion API error body. Skip the Notion write. Do not block the Zendesk ticket close. Post an alert to SLACK_CHANNEL_ESCALATIONS with the ticket ID and the failed payload for manual log entry.
Notion page create (resolution log)
HTTP 401 or 403 (integration not authorised on database)
Log the error immediately. Post an alert to SLACK_CHANNEL_ESCALATIONS. Suspend all Notion writes until the integration is re-added to the target database and the connection is validated. Do not retry automatically; require manual re-confirmation.
Overdue SLA poll (Zendesk search)
Search API returns no results unexpectedly (possible query error)
Log the raw query and the empty response. Do not treat an empty result as a confirmed all-clear. Post a warning to SLACK_CHANNEL_ESCALATIONS if two consecutive poll cycles both return zero open tickets while the Zendesk ticket count is known to be non-zero. Require manual verification.
Any integration
Unhandled exception or uncaught runtime error in orchestration layer
Catch all unhandled exceptions at the workflow level. Log the full stack trace and the triggering payload. Post an alert to SLACK_CHANNEL_ESCALATIONS with the workflow name, step name, error type, and ticket ID if available. Never fail silently. The Zendesk ticket must remain open and unassigned so a human can act on it.
For support with any integration issue or credential question, contact the FullSpec team at support@gofullspec.com. Include the workflow name, the affected ticket ID, and the error log excerpt in your message.
Integration and API SpecPage 3 of 3