Ticket de uso único para abrir o WebSocket da plataforma
POST/api/realtime/ticket
- Auth
- Requiere token
- Quién puede:
- owner
- admin
- Clave de API:
- No acepta clave de API
- X-CommunityId
X-CommunityIdEnvía el X-CommunityId de la comunidad en la que ocurre la operación.
Qualquer membro da comunidade (todos os papéis), **com o chat ligado ou desligado**. Devolve um ticket de **uso único**, válido por 60 s, preso à pessoa, a esta comunidade e à geração da sessão. Abra `wss://<api>/api/realtime/socket` (`data.path`) passando o ticket como segundo subprotocolo: `new WebSocket(url, ['memberfy-realtime', ticket])` (`data.protocols`); o servidor responde com `memberfy-realtime`. O ticket nunca vai na URL (na query ele é ignorado). Sem ticket válido a conexão é recusada antes de virar WebSocket (401). Trocar de comunidade fecha o socket e pede outro ticket.
**Salas.** Toda conexão já está em `me` (a própria pessoa), `community` e, para a equipe (owner, admin, moderador, financeiro), `staff`. As demais são pedidas com `{ "type": "subscribe", "rooms": [...] }` e `{ "type": "unsubscribe", "rooms": [...] }`: `space:<id>` (quem abre o espaço) e `item:event:<id>` / `item:post:<id>` (quem abre o evento ou o post), com a mesma regra do `GET` correspondente, sempre nesta comunidade. No máximo 20 salas por conexão. Resposta: `subscribed` (`rooms`) e um `subscribe.denied` (`room`) por sala recusada, **sem motivo**. Quando o acesso da pessoa muda (saiu, novo papel, grupo, acesso do espaço), o servidor reavalia as salas e manda `subscribe.denied` das que ela perdeu, e então `access.changed` (`{}`).
**Avisos de mudança** (`invalidate`): `{ "type": "invalidate", "room": "community", "topic": "event.live", "ids": ["<id>"], "at": "<iso>" }`. Só ids e o nome do tópico, nunca título, texto, valor ou nome; refaça o `GET` do que está na tela. Tópicos: `event.live` (evento começou ou terminou) e `events` (criado, editado, removido) em `community`, `space:<id>` e `item:event:<id>`, só para quem vê aquele espaço (evento privado ou em moderação: só a equipe); `analytics.online` em `staff` para owner e admin, quando o "Online agora" muda (no máximo 1 a cada 5 s); `campaigns.stats` em `staff` quando um envio termina; `menu.changed` em `community` quando o menu muda (seção ou espaço criado, editado, movido, reordenado ou removido, acesso ou visibilidade, modo "um ou vários" de um espaço, extensão ligada ou desligada), com `ids: []`, `communityId` e `reason` (`section.created`, `section.updated`, `section.deleted`, `sections.reordered`, `space.created`, `space.updated`, `space.deleted`, `spaces.reordered`, `space.mode`, `entitlements` ou `multiple`), no máximo cerca de 1 por segundo por comunidade: refaça `GET /api/sections`, que já filtra pelo seu acesso.
Salas de item também para `item:course:<id>` e `item:form:<id>` (a equipe e quem gerencia o formulário; um membro quando está publicado e ele abre o espaço) e, só para os papéis das rotas do editor, `item:campaign:<id>`, `item:email:<id>` (biblioteca), `item:workflow:<id>`, `item:survey:<id>` (owner, admin), `item:certificateTemplate:<id>` (quem emite no espaço, pelo `whoCanIssue`) e `item:emailOverride:<emailKey>` (e-mail padrão, owner e admin). Salas de área (telas de seção): `area:home` (todos), `area:members`, `area:campaigns`, `area:emails`, `area:workflows` (equipe), `area:moderation` (owner, admin, moderador), `area:analytics`, `area:dashboard`, `area:settings` (owner, admin) e `area:finance` (owner, admin, financeiro).
**Quem está aqui** (presença de área). O cliente manda `{ "type": "presence.join", "room": "<sala>", "editing": false }` ao abrir a tela (a sala é avaliada como no `subscribe` e assinada se ainda não estava; no máximo 3 lugares por conexão) e `{ "type": "presence.leave", "room": "<sala>" }` ao sair. Respostas: `presence.joined` (`room`, `editing`) seguido de um retrato `presence`, ou `presence.denied` (`room`), sem motivo. Não precisa de heartbeat: o ping do servidor mantém o lugar, que some 45 s depois da última resposta (fechar a conexão tira na hora). **Atividade:** `{ "type": "presence.active" }` quando há interação (tecla, clique, foco), no máximo a cada 10 s; entrar conta como atividade. A cada mudança (no máximo 1 por segundo por sala) a sala recebe, montado para cada conexão: `{ "type": "presence", "room": "area:campaigns", "area": "area:campaigns", "members": [{ "memberId", "name", "avatarUrl", "editing", "state": "active" | "idle", "lastActiveAt" }], "count": 3, "editingCount": 1, "at" }`. Você nunca está na própria lista; `members` traz no máximo 5 pessoas (editando ativas primeiro, depois as ativas, depois as ociosas) e `count` é o total das outras pessoas na sala. `idle` = conectada, sem atividade há 30 s ou mais (continua na lista; a troca de estado é avisada). **Privacidade:** quem está com o status do chat desligado ("Disponível para conversar" desligado) não aparece nem conta para os outros, mas vê quem está; bloqueio no chat esconde as duas pessoas uma da outra.
**Edição.** Num editor (campanha, e-mail da biblioteca, automação, evento, curso, formulário, pesquisa, modelo de certificado, configurações em `area:settings`), entre com `"editing": true` (só vale para a equipe e, no formulário, para quem o gerencia). Quem está editando aparece com `editing: true` para os **outros editores** da sala mesmo com o status do chat desligado ("X também está editando"); quem só lê a página não fica sabendo. `editingCount` conta só quem edita **ativo** (um editor ocioso continua listado com `editing`). Em sala de item ou de espaço, quem não está editando ali recebe só `memberId`, `name`, `avatarUrl` e `state` (sem `editing`, `lastActiveAt` nem `editingCount`). Repetir `presence.join` com o mesmo `editing` só devolve `presence.joined`, sem novo retrato. **Chat da comunidade desligado:** a presença não mostra ninguém. Salas de área e quem só lê uma sala de item ou de espaço recebem `presence` com `members: []` e `count: 0` (para todos, equipe incluída). Só quem está editando continua vendo os **outros editores** do mesmo item (`members`, `count` e `editingCount` só com eles), e o `item.saved` e a checagem de versão (409) seguem iguais. Ligar ou desligar o chat (`chat.settings`) manda um retrato novo de cada sala da conexão na hora. A cada gravação desses itens a sala recebe, para a equipe e para quem está editando: `{ "type": "item.saved", "topic": "item.saved", "room": "item:campaign:<id>", "itemType": "campaign", "itemId": "<id>", "by": { "memberId", "name" }, "updatedAt": "<nova versão>", "at" }` ("Fulano salvou alterações — recarregar"; compare `updatedAt` com a versão que você tem para ignorar a sua própria). `itemType`: `event`, `campaign`, `email`, `workflow`, `course` (inclusive módulos e aulas), `form`, `survey`, `certificateTemplate`, `emailOverride` (`itemId` = o `emailKey`) e, na sala `area:settings` (owner e admin, `itemId` = a comunidade), `settings` (a linha da comunidade: perfil, marca, imagens, diretório, remetentes das automações, domínio), `chatSettings`, `notificationSettings`, `messagingSettings` (SMS e WhatsApp), `creditSettings`, `businessInformation` e `payoutSettings`. As gravações aceitam a versão lida (`updatedAt` no corpo, ou `settingsUpdatedAt` na linha da comunidade, ou o cabeçalho `If-Unmodified-Since`) e respondem **409** com a versão atual em `data` quando outra gravação veio antes; cada resposta de sucesso traz a nova versão.
**Outros eventos** (JSON `{ type, data }`): `ready` (`profileId`, `communityId`, `chatAvailable`, `chatEnabled`, `rooms`, `maxRooms`, `pingIntervalMs`), `notification.new` / `notification.read` / `notification.removed` (o sino, em toda comunidade), `ai.dropDescription.ready`, `chat.settings`, `session.revoked` e `pong`. Os eventos do chat (`message.*`, `typing`, `read`, `conversation.*`, `presence.delta`, `me.updated`, `connection.*`, descritos em `POST /api/chat/ticket`) só chegam com o chat **ligado**: com `chatEnabled: false` no `ready` (ou depois de `chat.settings` com `enabled: false`) o socket continua de pé e só os eventos de chat param. **Do cliente:** `subscribe`, `unsubscribe`, `presence.join`, `presence.leave`, `presence.active`, `presence.idle` (`{ "idle": true|false }`, Ausente automático do chat: ver `POST /api/chat/ticket`), `typing` (só com o chat ligado) e `ping`; quadros de até 4 KB, sem binário, no máximo 5 por segundo.
**Códigos de fechamento:** 4004 sessão encerrada (não reconecte com a mesma sessão), 4009 substituído (mais de 10 conexões da pessoa no servidor; a mais antiga fecha), 4029 servidor cheio (conecte de novo mais tarde e use as rotas enquanto isso), 1008 excesso de quadros, 1001 servidor reiniciando. O servidor manda `ping` a cada 25 s; reconecte com espera aleatória e backoff, pedindo ticket novo. Sem Redis: 503, e o app segue buscando pelas rotas.
Headers
| Nombre | Tipo | Descripción |
|---|---|---|
X-CommunityIdopcional | string (uuid) | ID da comunidade em que a operação acontece. Obrigatório na maioria dos endpoints com escopo de comunidade. |
Cuerpo de la solicitud application/json
La especificación no declara los campos de este cuerpo (una subida multipart, por ejemplo). Consulta la guía del tema.
Respuestas
| Código | Descripción |
|---|---|
201 | Ticket emitido. |
401 | Token ausente, inválido ou expirado; token de outra comunidade (o `X-CommunityId` ou o `communityId` do corpo não é o do token); token antigo ou de uma sessão encerrada (troca de senha, convite aceito). Nesses casos a mensagem pede para entrar de novo (`auth.signInAgain`). |
403 | Autenticado, mas sem permissão para esta operação. |
500 | Erro interno do servidor. |
503 | Tempo real fora do ar (sem Redis); use as rotas. |
Ejemplo con curl
curl -X POST "https://api.memberfy.net/api/realtime/ticket" \
-H "Authorization: Bearer $TOKEN" \
-H "X-CommunityId: $COMMUNITY_ID" \
-H "Content-Type: application/json" \
-d '{}'