API

Tickets a partir de seus próprios sistemas

Um ticket aberto pela API é um ticket de verdade: cria um canal no Discord, seu time responde normalmente e a resposta volta direto para o visitante. Ele nunca precisa de uma conta 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" }
      }'
Resposta
{
  "ticket_request_id": 4102,
  "session_token": "3Qk1r…",
  "status": "pending"
}

O canal aparece em poucos segundos. Guarde o session_token — é a única coisa que deixa aquele visitante ler e responder seu ticket.

Duas chaves, dois trabalhos

Qual você envia decide o que a chamada pode fazer.

X-API-Key

Sua chave do painel. Cobre todo o servidor: abrir tickets, lê-los, responder como time.

Fica no seu servidor e em lugar nenhum mais. Num navegador, deixa qualquer visitante ler cada ticket.

X-Session-Token

Volta quando você abre um ticket e cobre exatamente aquele ticket — não o servidor.

Esse é seguro para passar para o visitante cujo ticket é.

Permissões por chave

Você marca o que a chave pode fazer ao criá-la. Nunca pode fazer mais.

  • ticket:createAbrir tickets
  • ticket:readLer tickets, mensagens e painéis
  • ticket:replyEscrever num ticket como time
  • stats:readLer números agregados

Uma permissão ausente retorna 403, não 401. A chave está bem — só falta essa marca.

Envie apenas os campos documentados aqui. Um nome de campo desconhecido não é silenciosamente ignorado, mas rejeitado com 422 — um erro de digitação deve aparecer imediatamente, não como um ticket vazio.

Os endpoints

Seis chamadas cobrem todo o fluxo. A referência completa com cada campo fica ligada embaixo.

POST/v1/ticketsAbrir um ticket e receber um token de sessão
GET/v1/ticketsListar tickets, paginados por cursor
GET/v1/tickets/{id}Um ticket com sua conversa
POST/v1/tickets/{id}/messagesResponder como time — a rota para e-mail, CRM ou helpdesk
GET/v1/panelsListar painéis para achar o panel_id
GET/v1/statsNúmeros para seu próprio relatório

Limites

Contados por chave, não por IP — então todos seus clientes podem compartilhar um backend. Cada resposta diz quanto você tem sobrando.

Em cada resposta
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 47
Retry-After: 47

Tentativas

Quando uma requisição expira, você não sabe se o ticket foi criado. Mande uma chave que você escolhe e a tentativa retorna a mesma resposta em vez de um segundo ticket.

Ao abrir um ticket
Idempotency-Key: 7f3a1c9e-4b21-4f0a-9d55-1e8c2b7a6d40

Guardamos a chave por 24 horas. Reusar uma chave para uma requisição genuinamente diferente retorna 422 — melhor um erro que o número errado do ticket.

Webhooks: quando algo deve chegar do seu lado

A direção oposta: em vez de você perguntar, o Ticketanizer te chama. Cada evento sai como um POST assinado para uma URL à sua escolha — para um bridge de e-mail, um CRM, ou simplesmente uma notificação no seu próprio sistema.

POST sua 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 em 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 é único por entrega — guarde e você consegue identificar uma repetição e evitar processar duas vezes.

sent_at e created_at em webhooks estão em UTC. A API de leitura ainda retorna horários no horário do servidor sem deslocamento — tenha isso em mente ao mesclar pushes de webhook com polling.

Verificando a assinatura

Calcule HMAC-SHA256 sobre <timestamp>.<rawBody> com seu segredo e compare o resultado contra um dos valores v1. Duas coisas fáceis de perder: o header pode carregar vários valores v1 (durante uma rotação de segredo), e a comparação deve rodar em tempo 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)));
}

Assinamos o corpo raw — exatamente os bytes que chegam. Verificar depois de parsear e re-serializar o JSON produz uma assinatura diferente e nada além de falhas.

Os eventos

Você marca o que quer quando cria o endpoint. Sem seleção você recebe todos os eventos — com uma exceção, veja abaixo.

createdTicket aberto
claimed · unclaimedTicket atribuído ou liberado novamente
closed · reopenedTicket fechado ou reabertor
escalatedTicket repassado para outro painel
priority · member_added · member_removedPrioridade mudada, alguém adicionado ou removido do ticket
feedbackAvaliação dada após fechamento
message.createdToda mensagem no ticket, texto completo. Apenas quando explicitamente marcado — conteúdo de conversa não deve fluir sem permissão para um endpoint que alguém configurou para mudanças de status.
deleted · transcript · snoozedTicket deletado, transcrição armazenada, ticket suspenso. Se o seu sistema espelha tickets, marque deleted também — caso contrário, um ticket deletado ficará aberto do seu lado para sempre.

O que saber sobre entrega

  • TentativasResponda com um status 2xx. Qualquer outra coisa conta como falha e tentamos novamente — após 1, 5, 15 e 60 minutos, depois não mais.
  • Proteção contra loopCada mensagem traz uma origin (discord, web, inbox, relay). Se você responder em reação a um evento, filtre os seus próprios — senão sua resposta dispara o próximo evento.
  • DuplicatasResponda muito devagar e a mesma entrega chega novamente. Isto é deliberado: duas vezes é melhor que nenhuma. O ID de entrega é o que te protege de processar duas vezes.
  • Endpoints mortosApós 20 entregas seguidas que falham de verdade, desligamos o endpoint e escrevemos o motivo no dashboard. Uma breve interrupção não faz isso — a contagem começa apenas quando todas as tentativas se esgotam.
  • Rotacionando o segredoQuando você rotaciona, o segredo antigo permanece válido por 7 dias e assinamos com ambos. Isso deixa você trocar com calma sem perder uma entrega.

Entregas são visíveis no dashboard — incluindo o payload enviado e a resposta do seu servidor. De lá você também pode repetir uma entrega manualmente, contanto que ainda esteja no log.

A referência completa

Cada endpoint, campo e código de erro — gerado do serviço rodando e testável direto no navegador.