Webhooks
Receba notificações em tempo real sobre eventos da academia.
Webhooks
Webhooks permitem que você receba notificações em tempo real sobre eventos importantes da sua academia: novos alunos, mensalidades ativadas/canceladas, check-ins, reservas de aulas e muito mais.
Configuração via painel
O gerenciamento de webhooks via API ainda não está disponível. Configure seus webhooks diretamente pelo painel do OctaGym em Configurações → Webhooks.
Como Funciona
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ Evento Ocorre │────>│ OctaGym │────>│ Seu Servidor │
│ (ex: aluno criado, │ │ envia POST │ │ processa e │
│ check-in, etc.) │ │ para sua URL │ │ responde 200 OK │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘- Um evento ocorre na plataforma (aluno matriculado, check-in realizado, etc.)
- O OctaGym envia uma requisição
POSTpara a URL configurada - Seu servidor processa o evento e responde com
200 OK - Se falhar, tentamos novamente com backoff exponencial
Eventos Disponíveis
Eventos de Alunos (Members)
| Evento | Quando é Disparado |
|---|---|
member.created | Novo aluno cadastrado |
member.updated | Dados do aluno alterados |
member.status_changed | Status alterado (ativo, inativo, trial) |
Eventos de Mensalidades (Memberships)
| Evento | Quando é Disparado |
|---|---|
membership.created | Nova mensalidade criada |
membership.activated | Mensalidade ativada (pagamento confirmado) |
membership.cancelled | Mensalidade cancelada |
membership.frozen | Mensalidade congelada |
membership.unfrozen | Mensalidade descongelada |
membership.past_due | Mensalidade em atraso (pagamento vencido) |
Eventos de Aulas (Classes)
| Evento | Quando é Disparado |
|---|---|
class.booking_created | Reserva de aula realizada |
class.booking_cancelled | Reserva de aula cancelada |
Eventos de Check-in
| Evento | Quando é Disparado |
|---|---|
checkin.created | Check-in realizado (catraca, app ou manual) |
Formato do Payload
Todas as notificações seguem este formato:
{
"id": "evt_abc123def456",
"type": "member.created",
"api_version": "v1",
"created_at": "2026-03-22T14:30:00Z",
"data": {
"object": {
"id": "uuid-do-aluno",
"name": "Ana Oliveira",
"email": "ana@email.com",
"status": "trial",
"created_at": "2026-03-22T14:30:00Z"
},
"previous_attributes": null
},
"team_id": "uuid-da-academia",
"webhook_id": "uuid-do-webhook"
}Para eventos de atualização, previous_attributes contém os valores anteriores dos campos alterados:
{
"id": "evt_update789",
"type": "member.status_changed",
"api_version": "v1",
"created_at": "2026-03-22T15:00:00Z",
"data": {
"object": {
"id": "uuid-do-aluno",
"name": "Ana Oliveira",
"status": "active"
},
"previous_attributes": {
"status": "trial"
}
},
"team_id": "uuid-da-academia",
"webhook_id": "uuid-do-webhook"
}Exemplo: Mensalidade cancelada
{
"id": "evt_cancel456",
"type": "membership.cancelled",
"api_version": "v1",
"created_at": "2026-03-22T10:15:00Z",
"data": {
"object": {
"id": "uuid-da-mensalidade",
"member_id": "uuid-do-aluno",
"plan_id": "uuid-do-plano",
"status": "cancelled",
"cancelled_at": "2026-03-22T10:15:00Z",
"cancellation_reason": "Mudança de cidade"
},
"previous_attributes": {
"status": "active"
}
},
"team_id": "uuid-da-academia",
"webhook_id": "uuid-do-webhook"
}Exemplo: Check-in realizado
{
"id": "evt_checkin789",
"type": "checkin.created",
"api_version": "v1",
"created_at": "2026-03-22T07:32:00Z",
"data": {
"object": {
"id": "uuid-do-checkin",
"member_id": "uuid-do-aluno",
"member_name": "Bruno Santos",
"method": "qr_code",
"checked_in_at": "2026-03-22T07:32:00Z"
},
"previous_attributes": null
},
"team_id": "uuid-da-academia",
"webhook_id": "uuid-do-webhook"
}Verificando a Assinatura
Para garantir que a notificação veio do OctaGym, verifique a assinatura HMAC-SHA256 no header X-OctaGym-Signature.
Headers Enviados
| Header | Descrição |
|---|---|
X-OctaGym-Signature | Assinatura HMAC-SHA256 |
X-OctaGym-Timestamp | Timestamp Unix do envio |
X-OctaGym-Event-Id | ID único do evento |
Content-Type | application/json |
A assinatura é calculada sobre a string {timestamp}.{payload} usando o webhook_secret que você recebe ao criar o webhook no painel.
Verificação em Node.js
import crypto from 'crypto';
import express from 'express';
const app = express();
const WEBHOOK_SECRET = process.env.OCTAGYM_WEBHOOK_SECRET;
app.use('/webhook', express.raw({ type: 'application/json' }));
app.post('/webhook', (req, res) => {
const signature = req.headers['x-octagym-signature'];
const timestamp = req.headers['x-octagym-timestamp'];
const payload = req.body.toString();
// Verificar timestamp (previne replay attacks — janela de 5 minutos)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
return res.status(401).json({ error: 'Timestamp expirado' });
}
// Calcular assinatura esperada
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(signedPayload)
.digest('hex');
// Comparação segura (previne timing attacks)
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
return res.status(401).json({ error: 'Assinatura inválida' });
}
// Processar evento
const event = JSON.parse(payload);
switch (event.type) {
case 'member.created':
// Enviar email de boas-vindas, notificar equipe, etc.
break;
case 'membership.cancelled':
// Registrar motivo, disparar fluxo de retenção, etc.
break;
case 'checkin.created':
// Atualizar contador de ocupação, registrar frequência, etc.
break;
case 'membership.past_due':
// Enviar lembrete de pagamento, bloquear acesso, etc.
break;
}
// Responda RAPIDAMENTE com 200 OK
res.status(200).json({ received: true });
});Verificação em Python
import hmac
import hashlib
import time
import os
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = os.environ['OCTAGYM_WEBHOOK_SECRET']
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-OctaGym-Signature')
timestamp = request.headers.get('X-OctaGym-Timestamp')
payload = request.get_data(as_text=True)
# Verificar timestamp (janela de 5 minutos)
now = int(time.time())
if abs(now - int(timestamp)) > 300:
return jsonify({'error': 'Timestamp expirado'}), 401
# Calcular assinatura esperada
signed_payload = f"{timestamp}.{payload}"
expected_signature = hmac.new(
WEBHOOK_SECRET.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
# Comparação segura
if not hmac.compare_digest(signature, expected_signature):
return jsonify({'error': 'Assinatura inválida'}), 401
event = request.json
match event['type']:
case 'member.created':
# Enviar email de boas-vindas
pass
case 'membership.cancelled':
# Fluxo de retenção
pass
case 'checkin.created':
# Registrar frequência
pass
return jsonify({'received': True}), 200Política de Retentativas
Se sua URL não responder com 2xx, tentaremos novamente com backoff exponencial:
| Tentativa | Intervalo |
|---|---|
| 1a | Imediato |
| 2a | 1 minuto |
| 3a | 5 minutos |
| 4a | 30 minutos |
Após 3 tentativas falhas consecutivas, o evento é marcado como falho. Após múltiplas falhas seguidas, o webhook pode ser desativado automaticamente. Você receberá um email de alerta.
Boas Práticas
- Responda rapidamente — Retorne
200 OKem até 5 segundos. Não processe a lógica de negócio antes de responder. - Processe de forma assíncrona — Use filas de mensagens (Bull, Celery, SQS, etc.) para tarefas demoradas. Receba o webhook, enfileire e responda
200. - Implemente idempotência — O mesmo evento pode ser entregue mais de uma vez. Use o campo
iddo evento para evitar processamento duplicado. - Verifique a assinatura — Sempre valide a autenticidade com o
X-OctaGym-Signatureantes de processar qualquer evento. - Use HTTPS — Obrigatório para receber webhooks em produção.
- Monitore falhas — Configure alertas para quando webhooks falharem repetidamente.
Suporte
- Email: suporte@octagym.ai
- Dashboard: dashboard.octagym.ai