n4nAI

Integrating LLM APIs with Zendesk and Intercom

Step-by-step guide to building an LLM API Zendesk Intercom integration: webhooks, normalization, inference, fallback, and reply posting, with runnable code.

n4n Team4 min read861 words

Audio narration

Coming soon — every post will get a voice note here.

Wiring an LLM API Zendesk Intercom integration is less about the model and more about plumbing: both help desks push events differently, expect different reply shapes, and rate-limit aggressively. This guide walks through a single Python service that ingests webhooks from either platform, calls an OpenAI-compatible endpoint, and writes suggestions back as private notes or public replies.

Step 1: Stand up a webhook receiver

You do not want to hack logic into Zendesk’s marketplace app or Intercom’s out-of-the-box bot. Build a small HTTP service that both platforms can POST to. FastAPI is enough for most teams shipping a first version.

from fastapi import FastAPI, Request, HTTPException
import os

app = FastAPI()

@app.post("/zendesk")
async def zendesk_webhook(req: Request):
    # Zendesk sends a static token header you configure in the admin UI
    token = req.headers.get("X-Zendesk-Token")
    if token != os.environ["ZENDESK_WEBHOOK_TOKEN"]:
        raise HTTPException(status_code=401)
    payload = await req.json()
    from tasks import process_zendesk
    process_zendesk.delay(payload)
    return {"ok": True}

@app.post("/intercom")
async def intercom_webhook(req: Request):
    payload = await req.json()
    # Intercom sends a CRC challenge on subscription; answer it
    if payload.get("type") == "notification_event":
        from tasks import process_intercom
        process_intercom.delay(payload)
    return {"ok": True}

Run it behind gunicorn with uvicorn workers. Expose /zendesk and /intercom to the internet via a tunnel or load balancer. In Zendesk, create a trigger with conditions like “Ticket is created” or “Ticket status changed” and an action “Notify target” pointing at your URL with X-Zendesk-Token. In Intercom, subscribe to conversation.user.created and conversation.user.replied webhook topics in the Developer Hub.

Verify: curl -XPOST localhost:8000/zendesk -H "X-Zendesk-Token: test" -d '{"ticket":{"id":1}}' returns {"ok":true}. Check your task queue logs to confirm the job was enqueued.

Step 2: Normalize events to a common schema

Zendesk gives you ticket.id, ticket.subject, ticket.comments. Intercom gives you conversation.id, conversation.parts. Do not let these shapes leak into your LLM code. Define one internal structure so the rest of the pipeline is vendor-agnostic.

from pydantic import BaseModel
from typing import List, Literal

class Message(BaseModel):
    role: Literal["user", "agent", "system"]
    text: str

class SupportEvent(BaseModel):
    platform: Literal["zendesk", "intercom"]
    external_id: str
    messages: List[Message]
    is_internal: bool = False  # True for agent-only notes

Mapping Zendesk comments:

def map_zendesk(payload: dict) -> SupportEvent:
    ticket = payload["ticket"]
    msgs = []
    for c in ticket.get("comments", []):
        role = "agent" if c.get("author_role") == "agent" else "user"
        msgs.append(Message(role=role, text=c["body"]))
    return SupportEvent(
        platform="zendesk",
        external_id=str(ticket["id"]),
        messages=msgs,
    )

Intercom parts have part_type of comment (customer or agent) or note (internal). Mark notes as is_internal=True. Normalizing early means your prompt builder and API caller stay identical for both integrations. It also makes the LLM API Zendesk Intercom integration testable without mocking either vendor’s SDK.

Step 3: Call the LLM with conversation history

Use the OpenAI Python client but point base_url at whatever endpoint you choose. For a single vendor, that is https://api.openai.com/v1. If you want one endpoint that addresses 240+ models with automatic fallback when a provider is rate-limited or degraded, an OpenRouter-class gateway such as n4n.ai collapses that complexity into a single base_url and honors the same /chat/completions shape, forwarding provider cache-control hints without extra code.

from openai import OpenAI

client = OpenAI(
    base_url=os.environ["LLM_BASE_URL"],
    api_key=os.environ["LLM_API_KEY"],
)

SYSTEM_PROMPT = (
    "You are a support assistant. Answer concisely, cite policy, "
    "and never invent order numbers."
)

def generate_reply(event: SupportEvent) -> str:
    chat = [{"role": "system", "content": SYSTEM_PROMPT}]
    for m in event.messages:
        if m.role == "agent" and event.is_internal:
            chat.append({"role": "system", "content": f"Agent note: {m.text}"})
        else:
            chat.append({"role": m.role, "content": m.text})
    resp = client.chat.completions.create(
        model=os.environ["LLM_MODEL"],
        messages=chat,
        max_tokens=300,
        temperature=0.2,
    )
    return resp.choices[0].message.content.strip()

Keep max_tokens low; support replies should be short. Set temperature near zero for factual consistency. If you call the API synchronously inside the webhook, you risk timeouts—offload to a worker (Celery/ARQ) as hinted in Step 1. A robust LLM API Zendesk Intercom integration treats the model as a pure function: event -> string. That isolation lets you swap models or vendors without touching the webhook or reply code.

Step 4: Route responses back to the correct platform

Once you have the generated text, push it back. Zendesk expects a ticket comment; Intercom expects a conversation reply or note.

Zendesk:

import requests

def post_zendesk_reply(ticket_id: str, text: str, internal: bool):
    url = f"https://{os.environ['ZENDESK_SUBDOMAIN']}.zendesk.com/api/v2/tickets/{ticket_id}/comments.json"
    body = {"comment": {"body": text, "public": not internal}}
    r = requests.post(url, json=body,
                      auth=(os.environ["ZENDESK_EMAIL"], os.environ["ZENDESK_TOKEN"]))
    r.raise_for_status()

Intercom:

def post_intercom_reply(conversation_id: str, text: str, internal: bool):
    url = f"https://api.intercom.io/conversations/{conversation_id}/reply"
    body = {
        "message_type": "comment" if not internal else "note",
        "body": text,
        "from": {"type": "admin", "id": os.environ["INTERCOM_ADMIN_ID"]},
    }
    r = requests.post(url, json=body,
                      headers={"Authorization": f"Bearer {os.environ['INTERCOM_TOKEN']}"})
    r.raise_for_status()

Wire these into your task worker. If event.is_internal, post as a note only visible to agents; otherwise public reply. This completes the core LLM API Zendesk Intercom integration loop. Note that both APIs have strict rate limits (Zendesk: 700 req/min per email, Intercom: 10 req/s), so batch or throttle if you backfill historical tickets.

Step 5: Add guardrails and idempotency

Help desk webhooks retry on failure. Without idempotency you will spam customers with duplicate LLM replies.

  • Store event.external_id + hash of last message in Redis with a 1-hour TTL.
  • Before generating, check the key. If present, skip.
  • Redact obvious PII (emails, card numbers) with a regex before sending to the model. The model does not need a customer’s full email to suggest a reset-password link.
import re, hashlib
from redis import Redis

r = Redis.from_url(os.environ["REDIS_URL"])

def already_handled(event: SupportEvent) -> bool:
    last = event.messages[-1].text
    key = f"{event.platform}:{event.external_id}:{hashlib.md5(last.encode()).hexdigest()}"
    if r.exists(key):
        return True
    r.setex(key, 3600, "1")
    return False

EMAIL_RE = re.compile(r"[\w.]+@[\w.]+\.\w+")
def scrub(text: str) -> str:
    return EMAIL_RE.sub("[redacted]", text)

Apply scrub to each message before building the chat array. These steps are non-negotiable in production; skipping them is how you leak data or double-send. Also cap total input tokens at ~6k to avoid blowing context windows on long ticket threads—truncate oldest messages first.

Step 6: Deploy and verify end-to-end

Package the worker and API in the same container or separate pods. Use environment variables for all tokens. Set up a staging Zendesk instance and an Intercom sandbox.

Create a test ticket in Zendesk with subject “Password reset not working”. Your service should receive the webhook, generate a reply, and post a public comment. In Intercom, send a test conversation from a fake user. Confirm the bot note appears.

Verification script:

# Trigger Zendesk manually
curl -XPOST https://your-domain/zendesk \
  -H "X-Zendesk-Token: $ZENDESK_WEBHOOK_TOKEN" \
  -d '{"ticket":{"id":123,"comments":[{"author_role":"user","body":"I cannot log in"}]}}'

# Check ticket comments via API
curl -u "$ZENDESK_EMAIL:$ZENDESK_TOKEN" \
  https://$ZENDESK_SUBDOMAIN.zendesk.com/api/v2/tickets/123/comments.json | jq '.comments[-1].body'

If the last comment body contains your LLM’s text, the LLM API Zendesk Intercom integration works. For Intercom, use GET /conversations/{id} and assert the last part matches. Run the same test with is_internal=True to confirm notes stay private.

Operational notes

  • Both platforms emit high-volume webhooks. Use a queue (SQS, Redis Streams) between receiver and worker to absorb spikes.
  • Model latency varies. If a provider is slow, your worker should timeout at 8s and leave the ticket for human pickup. Gateways with automatic fallback reduce this risk but do not eliminate the need for a timeout.
  • Log every external_id, model used, and token count. Per-token metering (available on some gateways) lets you attribute cost to each support channel and spot abuse.
  • Add a dead-letter queue for failed posts so a transient Intercom 503 does not lose the draft reply.

The pattern above is deliberately boring. A reliable LLM API Zendesk Intercom integration is a normalized event pipe, a stateless generation call, and a typed reply poster—not a tangle of platform-specific SDK calls. Build it that way and you can swap models, add languages, or extend to Front or Help Scout without rewriting the core.

Tagszendeskintercomintegrationschatbots

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All best api for customer support chatbots posts →