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.

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" }
      }'
Respuesta
{
  "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 tickets
  • ticket:readLee tickets, mensajes y paneles
  • ticket:replyEscribe en un ticket como el equipo
  • stats: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/ticketsAbre un ticket y recibe un token de sesión
GET/v1/ticketsLista tickets, paginados a través de un cursor
GET/v1/tickets/{id}Un ticket incluyendo su conversación
POST/v1/tickets/{id}/messagesResponde como el equipo — la ruta para correo, CRM o un helpdesk
GET/v1/panelsLista paneles para encontrar el panel_id
GET/v1/statsNú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.

En cada respuesta
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.

Al abrir un 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.

POST tu 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"
  }
}
Headers en cada entrega
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.

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)));
}

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.

createdTicket abierto
claimed · unclaimedTicket reclamado o liberado de nuevo
closed · reopenedTicket cerrado o reabierto
escalatedTicket derivado a otro panel
priority · member_added · member_removedPrioridad cambiada, alguien añadido o eliminado del ticket
feedbackValoración dada después de cerrar
message.createdCada 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 · snoozedTicket 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 un origin (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.