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.
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" }
}'
{
"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 öffnenticket:readTickets, Nachrichten und Panels lesenticket:replyAls Team in ein Ticket schreibenstats: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/tickets | Ticket öffnen und Session-Token erhalten |
| GET | /v1/tickets | Tickets auflisten, seitenweise über einen Cursor |
| GET | /v1/tickets/{id} | Ein Ticket samt Gesprächsverlauf |
| POST | /v1/tickets/{id}/messages | Als Team antworten – der Weg für E-Mail, CRM oder Helpdesk |
| GET | /v1/panels | Panels auflisten, um die panel_id zu finden |
| GET | /v1/stats | Kennzahlen 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.
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.
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.
{
"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"
}
}
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.
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.
created | Ticket eröffnet |
claimed · unclaimed | Ticket übernommen bzw. wieder freigegeben |
closed · reopened | Ticket geschlossen bzw. wieder geöffnet |
escalated | Ticket an ein anderes Panel weitergereicht |
priority · member_added · member_removed | Priorität geändert, jemand zum Ticket hinzugefügt oder entfernt |
feedback | Bewertung nach dem Schließen abgegeben |
message.created | Jede 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 · snoozed | Ticket 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 einorigin(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.