API

Tickets aus deinen eigenen Systemen

Ein Ticket über die API ist ein echtes Ticket: Es entsteht ein Discord-Kanal, dein Team antwortet wie gewohnt, und die Antwort geht direkt an den Besucher zurück. Einen Discord-Account braucht er dafür nicht.

POST /v1/tickets
curl https://api.ticketanizer.com/v1/tickets \
  -H "X-API-Key: tk_dein_schluessel" \
  -H "Content-Type: application/json" \
  -d '{
        "panel_id": 3,
        "visitor_name": "Anna Berger",
        "first_message": "Meine Bestellung ist nicht angekommen.",
        "metadata": { "Bestellnummer": "2026-8891", "Tarif": "Pro" }
      }'
Antwort
{
  "ticket_request_id": 4102,
  "session_token": "3Qk1r…",
  "status": "pending"
}

Der Kanal entsteht Sekunden später. Bewahre das session_token auf – nur damit kann dieser eine Besucher sein Ticket lesen und beantworten.

Zwei Schlüssel, zwei Zwecke

Welchen du mitschickst, entscheidet, wofür der Aufruf gilt.

X-API-Key

Dein Schlüssel aus dem Dashboard. Er gilt für den ganzen Server: Tickets öffnen, lesen, als Team antworten.

Gehört ausschließlich auf deinen Server. Landet er im Browser, kann jeder Besucher alle Tickets lesen.

X-Session-Token

Kommt als Antwort auf das Öffnen eines Tickets und gilt für genau dieses eine Ticket – nicht für den Server.

Diesen darfst du dem Besucher geben, dessen Ticket es ist.

Rechte pro Schlüssel

Beim Anlegen hakst du an, was der Schlüssel darf. Mehr kann er danach nicht.

  • ticket:createTickets öffnen
  • ticket:readTickets, Nachrichten und Panels lesen
  • ticket:replyAls Team in ein Ticket schreiben
  • stats:readKennzahlen lesen

Fehlt ein Recht, antwortet die API mit 403 statt 401. Der Schlüssel stimmt dann – ihm fehlt nur das Häkchen.

Schick nur Felder, die hier dokumentiert sind. Ein unbekannter Feldname wird nicht stillschweigend ignoriert, sondern mit 422 abgelehnt — ein Tippfehler soll dir nicht als leeres Ticket auffallen, sondern sofort.

Die Endpunkte

Sechs Aufrufe decken den ganzen Ablauf ab. Die vollständige Referenz mit allen Feldern findest du am Ende der Seite.

POST/v1/ticketsTicket öffnen und Session-Token erhalten
GET/v1/ticketsTickets auflisten, seitenweise über einen Cursor
GET/v1/tickets/{id}Ein Ticket samt Gesprächsverlauf
POST/v1/tickets/{id}/messagesAls Team antworten – der Weg für E-Mail, CRM oder Helpdesk
GET/v1/panelsPanels auflisten, um die panel_id zu finden
GET/v1/statsKennzahlen für deine eigene Auswertung

Grenzen

Gezählt wird pro Schlüssel, nicht pro IP – alle deine Kunden dürfen also über einen Server laufen. Jede Antwort sagt dir, wie viel du noch übrig hast.

In jeder Antwort
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 47
Retry-After: 47

Wiederholungen

Läuft eine Anfrage in einen Timeout, weißt du nicht, ob das Ticket entstanden ist. Schick einen selbst gewählten Schlüssel mit, dann bekommst du bei der Wiederholung dieselbe Antwort statt eines zweiten Tickets.

Beim Öffnen eines Tickets
Idempotency-Key: 7f3a1c9e-4b21-4f0a-9d55-1e8c2b7a6d40

Wir merken uns den Schlüssel 24 Stunden. Denselben Schlüssel für eine andere Anfrage zu verwenden, beantworten wir mit 422 – lieber ein Fehler als die falsche Ticket-Nummer.

Webhooks: wenn bei dir etwas ankommen soll

Umgekehrte Richtung: Statt dass du fragst, meldet sich Ticketanizer bei dir. Jedes Ereignis geht als signierter POST an eine URL deiner Wahl — für eine E-Mail-Brücke, ein CRM oder einfach eine Benachrichtigung im eigenen System.

POST deine URL
{
  "event": "message.created",
  "sent_at": "2026-08-01T12:34:56Z",
  "guild_id": "1470847667327471738",
  "ticket": {
    "id": 42, "number": 17,
    "panel_id": 3, "panel_name": "Support",
    "status": "open", "priority": 2,
    "opener_id": "234...", "metadata": null
  },
  "message": {
    "id": 8891,
    "discord_message_id": "13...",
    "origin": "discord",
    "is_staff": true,
    "is_ai": false,
    "author_id": "234...",
    "author_bot": false,
    "author_name": "Lisa",
    "content": "Ich schaue mir das an.",
    "created_at": "2026-08-01T12:34:56Z"
  }
}
Header jeder Zustellung
X-Ticketanizer-Event: message.created
X-Ticketanizer-Delivery: 90183
X-Ticketanizer-Timestamp: 1785600896
X-Ticketanizer-Signature: t=1785600896,v1=9f2c…

X-Ticketanizer-Delivery ist bei jeder Zustellung eindeutig — merk dir den Wert, dann erkennst du eine Wiederholung und verarbeitest sie nicht doppelt.

sent_at und created_at im Webhook sind UTC. Die Lese-API liefert Zeiten derzeit noch in Serverzeit ohne Offset — wenn du Webhooks und Polling mischst, rechne beim Zusammenführen damit.

Signatur prüfen

Berechne HMAC-SHA256 über <timestamp>.<rawBody> mit deinem Secret und vergleiche das Ergebnis mit einem der v1-Werte. Zwei Dinge, die man leicht übersieht: Der Header kann mehrere v1-Werte enthalten (während einer Secret-Rotation), und der Vergleich sollte in konstanter Zeit laufen.

Node.js
const crypto = require("crypto");

function verify(rawBody, header, secret) {
  const parts = header.split(",").map(p => p.split("="));
  const ts = parts.find(([k]) => k === "t")?.[1];
  // v1 kann MEHRFACH vorkommen — waehrend einer Secret-Rotation
  const sigs = parts.filter(([k]) => k === "v1").map(([, v]) => v);
  if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

  const expected = crypto.createHmac("sha256", secret)
    .update(ts + "." + rawBody)
    .digest("hex");

  return sigs.some(s => s.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)));
}

Signiert wird der rohe Rumpf — genau die Bytes, die ankommen. Wer erst nach JSON-Parsen und Neuserialisieren prüft, bekommt eine andere Signatur und wundert sich über lauter Fehlschläge.

Welche Ereignisse es gibt

Beim Anlegen des Endpunkts hakst du an, was du bekommen willst. Ohne Auswahl bekommst du alle Ereignisse — mit einer Ausnahme, siehe unten.

createdTicket eröffnet
claimed · unclaimedTicket übernommen bzw. wieder freigegeben
closed · reopenedTicket geschlossen bzw. wieder geöffnet
escalatedTicket an ein anderes Panel weitergereicht
priority · member_added · member_removedPriorität geändert, jemand zum Ticket hinzugefügt oder entfernt
feedbackBewertung nach dem Schließen abgegeben
message.createdJede Nachricht im Ticket, mit Volltext. Nur nach ausdrücklichem Anhaken — Gesprächsinhalte sollen nicht ungefragt an einen Endpunkt fließen, den jemand für Statusmeldungen eingerichtet hat.
deleted · transcript · snoozedTicket gelöscht, Transcript gespeichert, Ticket zurückgestellt. Wenn dein System Tickets spiegelt, hake deleted mit an — sonst bleibt ein gelöschtes Ticket bei dir für immer offen.

Was du übers Zustellen wissen solltest

  • WiederholungAntworte mit einem 2xx-Status. Alles andere gilt als Fehlschlag, und wir versuchen es erneut — nach 1, 5, 15 und 60 Minuten, danach nicht mehr.
  • SchleifenschutzJede Nachricht trägt ein origin (discord, web, inbox, relay). Wer auf ein Ereignis hin selbst antwortet, filtert seine eigenen heraus — sonst löst die eigene Antwort das nächste Ereignis aus.
  • DoppelteAntwortest du zu langsam, kommt dieselbe Zustellung erneut. Das ist gewollt: lieber zweimal als gar nicht. Gegen doppelte Verarbeitung hilft die Delivery-ID.
  • Tote ZieleNach 20 endgültig fehlgeschlagenen Zustellungen in Folge schalten wir den Endpunkt ab und schreiben den Grund ins Dashboard. Eine kurze Störung reicht dafür nicht — gezählt wird erst, wenn alle Wiederholungen erschöpft sind.
  • Secret wechselnBeim Wechseln bleibt das alte Secret 7 Tage gültig, und wir signieren mit beiden. So stellst du in Ruhe um, ohne eine Zustellung zu verlieren.

Zustellungen sind im Dashboard einsehbar — mit gesendetem Payload und der Antwort deines Servers. Von dort lässt sich eine Zustellung auch von Hand wiederholen, solange sie im Protokoll steht.

Die vollständige Referenz

Alle Endpunkte, Felder und Fehlercodes – direkt aus dem laufenden Dienst erzeugt und im Browser ausprobierbar.