API
Tickets desde tus propios sistemas
Un ticket abierto a través de la API es un ticket real: crea un canal de Discord, tu equipo lo responde como siempre, y la respuesta vuelve directamente al visitante. Nunca necesitan una cuenta de Discord.
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"
}
El canal aparece segundos después. Guarda el session_token — es lo único que permite a ese visitante leer y responder su ticket.
Dos claves, dos trabajos
Cuál envíes decide qué está permitido que haga la llamada.
X-API-Key
Tu clave del panel. Cubre todo el servidor: abre tickets, léelos, responde como el equipo.
Pertenece a tu servidor y a ningún otro sitio. En un navegador, permite a cualquier visitante leer todos los tickets.
X-Session-Token
Vuelve cuando abres un ticket y cubre exactamente ese, no el servidor.
Es seguro dar este al visitante cuyo ticket es.
Permisos por clave
Marcas qué puede hacer la clave cuando la creas. Nunca puede hacer más.
ticket:createAbre ticketsticket:readLee tickets, mensajes y panelesticket:replyEscribe en un ticket como el equipostats:readLee números agregados
Un permiso que falta devuelve 403, no 401. La clave está bien — simplemente le falta esa casilla.
Solo envía campos documentados aquí. Un nombre de campo desconocido no se ignora silenciosamente sino que se rechaza con 422 — un error tipográfico debería aparecer inmediatamente, no como un ticket vacío.
Los endpoints
Seis llamadas cubren todo el flujo. La referencia completa con cada campo está enlazada al final.
| POST | /v1/tickets | Abre un ticket y recibe un token de sesión |
| GET | /v1/tickets | Lista tickets, paginados a través de un cursor |
| GET | /v1/tickets/{id} | Un ticket incluyendo su conversación |
| POST | /v1/tickets/{id}/messages | Responde como el equipo — la ruta para correo, CRM o un helpdesk |
| GET | /v1/panels | Lista paneles para encontrar el panel_id |
| GET | /v1/stats | Números para tu propio reporte |
Límites
Contados por clave, no por IP — así todos tus clientes pueden compartir un backend. Cada respuesta te dice cuánto te queda.
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 47
Retry-After: 47
Reintentos
Cuando una solicitud se agota, no puedes saber si el ticket se creó. Envía una clave que elijas tú y el reintento devuelve la misma respuesta en lugar de un segundo ticket.
Idempotency-Key: 7f3a1c9e-4b21-4f0a-9d55-1e8c2b7a6d40
Recordamos la clave durante 24 horas. Reutilizar una clave para una solicitud genuinamente diferente devuelve 422 — mejor un error que el número de ticket equivocado.
Webhooks: cuando algo debe llegar a tu lado
La otra dirección: en lugar de que tú preguntes, Ticketanizer te llama. Cada evento sale como un POST firmado a una URL de tu elección — para un puente de correo, un CRM, o simplemente una notificación en tu propio sistema.
{
"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 es único por entrega — guárdalo y podrás detectar una repetición y evitar procesarla dos veces.
sent_at y created_at en webhooks están en UTC. La API de lectura aún devuelve tiempos en hora del servidor sin una zona horaria — ten eso en cuenta cuando fusiones envíos de webhook con sondeo.
Verificando la firma
Calcula HMAC-SHA256 sobre <timestamp>.<rawBody> con tu secreto y compara el resultado contra uno de los valores v1. Dos cosas fáciles de pasar por alto: el header puede llevar varios valores v1 (durante una rotación de secreto), y la comparación debe ejecutarse en tiempo constante.
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)));
}
Firmamos el cuerpo raw — exactamente los bytes que llegan. Verificar después de parsear y re-serializar el JSON produce una firma diferente y nada más que fallos.
Los eventos
Marcas lo que quieres cuando creas el endpoint. Sin selección recibes cada evento — con una excepción, mira abajo.
created | Ticket abierto |
claimed · unclaimed | Ticket reclamado o liberado de nuevo |
closed · reopened | Ticket cerrado o reabierto |
escalated | Ticket derivado a otro panel |
priority · member_added · member_removed | Prioridad cambiada, alguien añadido o eliminado del ticket |
feedback | Valoración dada después de cerrar |
message.created | Cada mensaje en el ticket, texto completo. Solo cuando esté explícitamente marcado — el contenido de la conversación no debe fluir sin pedirlo a un endpoint que alguien configuró para cambios de estado. |
deleted · transcript · snoozed | Ticket eliminado, transcripción guardada, ticket pospuesto. Si tu sistema refleja tickets, marca deleted también — de lo contrario un ticket eliminado permanece abierto en tu lado para siempre. |
Lo que debes saber sobre entregas
ReintentosResponde con un estado 2xx. Cualquier otra cosa cuenta como un fallo e intentamos de nuevo — después de 1, 5, 15 y 60 minutos, y luego no más.Guardia de bucleCada mensaje lleva unorigin(discord,web,inbox,relay). Si respondes a un evento, filtra los tuyos — de lo contrario tu respuesta dispara el siguiente evento.DuplicadosRespondes demasiado lentamente y la misma entrega llega de nuevo. Es deliberado: dos veces es mejor que nada. El ID de entrega es lo que te protege de procesarlo dos veces.Endpoints muertosDespués de 20 entregas seguidas que fallan definitivamente, apagamos el endpoint y escribimos la razón en el dashboard. Una breve interrupción no lo hará — el conteo comienza solo cuando todos los reintentos se agotan.Rotando el secretoCuando rotes, el secreto antiguo sigue siendo válido durante 7 días y firmamos con ambos. Eso te permite cambiar tranquilamente sin perder una entrega.
Las entregas son visibles en el dashboard — incluyendo la carga útil enviada y la respuesta de tu servidor. Desde allí también puedes reproducir una entrega manualmente, siempre que siga estando en el registro.
La referencia completa
Cada endpoint, campo y código de error — generado desde el servicio en ejecución y comprobable directamente en el navegador.